Il n'en est pas de même dans le modèle relationnel. En effet, la nature des attributs référentiels intervenant dans une jointure objet peut permettre de constituer une sorte de filtre et limiter ainsi l'ensemble des valeurs possibles pour le champ objet correspondant.

Certains attributs référentiels peuvent faire partie de la clé de leur relation. On peut alors considérer qu'ils ont alors un double rôle. D'une part, leurs valeurs participent aux clés des objets de la classe correspondante. D'autre part, du fait de leur fonction d'attributs référentiels, ils permettent également de référencer d'autres objets.

Comme une valeur de clé ne peut plus être modifiée une fois qu'elle a été attribuée, les valeurs de ces attributs référentiels sont par conséquent définitivement fixées quand un objet acquiert la persistance. De ce fait, elles transforment la jointure objet en filtre. Dans le champ objet associé ne sont plus ensuite autorisés que les objets dont la référence est compatible avec la contrainte exprimée par la jointure en question. Une jointure objet comprenant un ou plusieurs attributs référentiels éléments de clé est dite *restreinte*.

Quand un champ objet est associé à une jointure objet restreinte, l'absence de valeur dans ce champ est exprimée en donnant aux attributs référentiels non éléments de clé la valeur `NULL`. Si tous les attributs référentiels sont éléments de clé, l'absence de valeur dans le champ n'est pas autorisée. On peut d'ailleurs ajouter que, dans ce dernier cas de figure, pour un objet persistant donné, tous les attributs référentiels ont une valeur fixée. La référence de l'objet contenu est donc entièrement déterminée et le champ correspondant ne peut donc recevoir qu'un seul objet possible : celui dont la référence est donnée par les attributs référentiels. Une jointure objet dont tous les attributs référentiels sont éléments de clé est dite *fixée*.

Un exemple de champ objet associé à une jointure objet restreinte est présentée en annexe A.2 car notre base sur les employés n'en comporte pas. Par contre, elle présente un exemple de jointure objet fixée.

Le champ *adresse* de la classe *Employé* est associé à la jointure $J[\text{emp} \to \text{address}]$. Cette jointure, dont la définition est $\text{emp.empno} = \text{address.empno}$, est fixée car tous les attributs référentiels qu'elle comprend, soit $\{\text{emp.empno}\}$, sont également éléments de clé.

Dans le modèle objet, cette association entre le champ *adresse* et une jointure fixée se traduit par le fait que tout objet *Employé* est inmanquablement et définitivement lié à un objet *Adresse* au moment où il devient persistant. L'absence de valeur ou le remplacement de l'objet *Adresse* autorisé par un autre ne sont pas admis. Le comportement du système lors d'une tentative de validation dans la base d'un objet ayant été modifié sans respecter ces règles est examiné en §4.2.2.2.

### 3.2.3.3 Correspondance du champ "ensemble d'objets"

Un champ de type "ensemble d'objets" représente lui aussi un lien entre objets. Il s'agit cette fois d'un lien multiple qui attache un ensemble d'objets à l'objet origine. Si les objets de l'ensemble sont d'une même classe connue, la correspondance de ce type de champ est également une chaîne de jointures entre tables principales. Cependant, cette chaîne associe cette fois à chaque n-uplet de la table origine un ensemble de n-uplets de la table
destination.

Encore une fois, les $N-1$ premières jointures de la chaîne sont élémentaires. Elles lient entre elles des relations élémentaires. La dernière jointure doit avoir comme table initiale une relation élémentaire de la classe possèdant le champ et comme table finale la relation principale de la classe des objets à référencer.

Si le champ associé permet seulement la consultation de données de la base, cette jointure n'est soumise à aucune restriction. Si le champ est classique, c'est-à-dire si les valeurs qui y sont contenues sont susceptibles d'être reportées ou mises à jour dans la base, la jointure doit être une jointure objet. Les restrictions qui lui sont imposées (cf. §3.2.3.2) sont nécessaires pour préciser encore une fois dans quelles conditions elle est autorisée à échouer.

La jointure objet n'est pas tenue d'être stricte, même s'il est préférable que ce soit le cas. En fait, seule une jointure représentant une liaison $1:N$ en a la possibilité; la restriction correspondante est impossible à respecter pour une jointure qui représente une liaison $N:M$.

Nous avons précédemment identifié une jointure $J[emp→dept]$, de la table emp vers la table dept (figure 3.11). Son expression peut tout aussi bien permettre la définition d'une jointure $J[dept→emp]$, de la table dept vers la table emp. Cette nouvelle jointure est la correspondance naturelle du champ personnel de la classe Département, destiné à recueillir les références des employés appartenant à ce département (figure 3.14). Comme nous l'avons vu, cette jointure, qui représente une liaison $1:N$, est stricte; elle vérifie les quatre restrictions précédemment décrites.

<table><thead><tr><th colspan="3">Table project</th></tr><tr><th>projno</th><th>pname</th><th>budget</th></tr></thead><tbody><tr><td>101</td><td>alpha</td><td>250000.</td></tr><tr><td>102</td><td>beta</td><td>175000.</td></tr><tr><td>103</td><td>gamma</td><td>95000.</td></tr><tr><td>...</td><td>...</td><td>...</td></tr></tbody></table>

<table><caption>Figure 3.13 : Tables project et emproj</caption><thead><tr><th colspan="2">Table emproj</th></tr><tr><th>projno</th><th>empno</th></tr></thead><tbody><tr><td>101</td><td>7566</td></tr><tr><td>103</td><td>7566</td></tr><tr><td>101</td><td>7788</td></tr><tr><td>...</td><td>...</td></tr></tbody></table>

Prenons un autre exemple de jointure représentant cette fois une liaison $N:M$. Notre base de données comprend aussi deux tables project et emproj. project est la table des projets existant au sein de l'entreprise. Chaque employé peut participer à plusieurs projets, participations qui sont précisées dans la table emproj (figure 3.13). Considérons une classe Projet associée à la relation project et comprenant un champ employés. Ce champ est destiné à recueillir, pour chaque projet, les références des employés y participant. Sa correspondance relationnelle est une jointure $J[project→emp]$ (figure 3.14) dont l'expression est :
```
project.projno = emproj.projno and emproj.empno = emp.empno
```

Cette jointure représente clairement une liaison N:M : un projet peut comprendre plusieurs employés et chaque employé peut participer à plusieurs projets. Dans la représentation relationnelle, la liaison N:M doit nécessairement s'exprimer sous forme d'une table indépendante dans laquelle chaque n-uplet représente un lien entre deux données. Ceci permet à chacune d'entre elles d'être en relation avec un nombre quelconque de données de l'autre ensemble. De ce fait, cette table indépendante ne peut pas être élémentaire pour l'une des deux classes. À chaque n-uplet de relation principale peut correspondre un nombre quelconque de n-uplet de cette table. Les quelques n-uplets de la relation *emproj* qui ont été présentés montrent qu'il y en a déjà deux qui correspondent au projet "alpha". La restriction imposée aux jointures objet strictes ne peut pas être vérifiée.

<table><caption>Figure 3.14 : Associations champ "ensemble d'objets" - chaîne de jointures</caption><thead><tr><th>Classe, champ</th><th></th><th>Jointures</th></tr></thead><tbody><tr><td>Département, personnel</td><td>↔</td><td>(J[dept→emp])</td></tr><tr><td>Projet, employés</td><td>↔</td><td>(J[project→emp])</td></tr></tbody></table>

On peut faire la remarque que la sémantique du lien qui lie un objet à un autre détermine le fait que le lien inverse mène soit à l'objet origine, soit à un ensemble d'objets dont fait partie l'objet origine (lien 1:1 ou 1:N). Une jointure exprimant un lien 1:N peut donc permettre de définir soit un champ de type "objet" dans l'une des deux classes, soit un champ de type "ensemble d'objets" dans l'autre. Si la jointure exprime un lien N:M, elle peut alors être la correspondance dans chaque classe d'un champ de type "ensemble d'objets".

Dans tous les cas, les deux définitions ne pourront être tolérées simultanément que si l'un des champs est déclaré accessible en lecture seulement. Cependant, il est utile de faire remarquer que, comme ces deux champs sont associés aux mêmes données relationnelles, la mise-à-jour dans la base de l'un des deux suffit pour que les deux aient une valeur correctement représentée par ces données relationnelles. À la charge du modèle objet client bien sûr de gérer la cohérence ou les éventuels liens inverses entre les deux champs.

Pour finir, signalons que les champs "ensemble d'objets" peuvent également être associés à des jointures restreintes ou fixées. Comme pour les champs de type "objet", ne pourront y être référencés que les objets qui vérifieront le filtre formé par les attributs référentiels contraints.

### 3.2.3.4 Correspondance du champ "ensemble de valeurs atomiques"

Un champ de type "ensemble de valeurs atomiques" est un champ multi-valué qui permet d'associer à chaque objet un ensemble de valeurs atomiques de même type. Nous
définissons comme correspondance à ce type de champ un attribut d'une table secondaire rattachée à la table principale par une chaîne de jointures (figure 3.15). Cette chaîne doit associer à chaque n-uplet de la relation principale un ensemble de n-uplets de la relation secondaire, desquels sont extraites les valeurs composant l'ensemble.

<table><caption>Figure 3.15 : Association champ "ensemble d'atomes" - attribut</caption><thead><tr><th>Classe, champ</th><th>Relation, attribut, Jointures</th></tr></thead><tbody><tr><td>Département, sites</td><td>deptsite.sname, (J[dept→deptsite])</td></tr></tbody></table>

Comme pour le champ de type "ensemble d'objets", les $N-1$ premières jointures de la chaîne doivent être élémentaires et représenter des liaisons fortes. Encore une fois, la $N$-ième ne peut pas être élémentaire puisqu'elle représente un lien 1:N ou N:M.

Un champ de ce type a été défini dans la classe **Département**. Il s'agit du champ **sites**, contenant, pour chaque instance, un ensemble de symboles représentant les noms des sites où se trouve implanté le département. Ce champ trouve naturellement sa correspondance au sein de la relation **deptsite** qui précisément gère dans la base les informations concernant les sites où sont représentés les départements (figure 3.16).

<table><caption>Figure 3.16 : Table deptsite</caption><thead><tr><th colspan="2">Table deptsite</th></tr><tr><th>sname</th><th>dept</th></tr></thead><tbody><tr><td>New-York</td><td>10</td></tr><tr><td>Boston</td><td>20</td></tr><tr><td>Chicago</td><td>30</td></tr><tr><td>Chicago</td><td>10</td></tr><tr><td>Los-Angeles</td><td>30</td></tr><tr><td>Austin</td><td>30</td></tr><tr><td>...</td><td>...</td></tr></tbody></table>

Cette correspondance relationnelle est l'attribut **deptsite.sname** (figure 3.15), rendu accessible de la table principale **dept** par la jointure $J[dept→deptsite]$ dont l'expression est :

$$dept.deptno = deptsite.dept$$

Cette jointure représente une liaison N:M.
### 3.2.3.5 Correspondances des champs "ensemble ordonné"

Tous les types de champs gérant des ensembles ordonnés d'éléments (valeurs atomiques ou objets) sont des champs de type "ensemble" pour lesquel l'ordre des éléments a de l'importance. La correspondance de chacun est donc presque identique à celle du type dont il se rapproche (mais qui n'est pas ordonné). On ordonne en plus les éléments de l'ensemble d'après les valeurs d'attributs de tables utilisées dans la jointure, en liaison forte (1:1) avec la table destination. Ces attributs de tri peuvent bien sûr faire tout simplement partie de la table destination.

Le premier définit l'ordre primaire, le second l'ordre secondaire, et ainsi de suite. Les éléments sont d'abord ordonnés en fonction de la valeur de l'attribut primaire de tri. En cas d'égalité, les éléments correspondants sont triés entre eux en fonction de la valeur de l'attribut secondaire, etc. Pour chaque attribut, l'ordre peut être ascendant ou descendant.

La classe **Département** présente un champ appartenant à cette catégorie. Il s'agit du champ **chefs** du type "ensemble ordonné d'objets", destiné, pour chaque département, à recueillir les références de ses cadres. La correspondance de ce champ est la jointure $J[dept→emp]$, déjà décrite en §3.2.3.3, qui, contrairement à ce qui se passe pour le champ **personnel**, ne sélectionne pas l'ensemble des employés de chaque département. En effet, elle se trouve indirectement précisée par la classe des objets à filtrer (**Cadre**, ici), ce qui la complète des contraintes de cette classe (cf. §4.1.2).

<table><caption>Figure 3.17 : Correspondance d'un champ "ensemble ordonné d'objets"</caption><thead><tr><th>Classe, champ</th><th>Jointures,</th><th>Attributs de tri</th></tr></thead><tbody><tr><td>Département, chefs</td><td>↔ (J[dept→emp])</td><td>(emp.sal (desc) emp.ename (asc))</td></tr></tbody></table>

Tous les attributs de **emp**, de **person** ou de n'importe quelle relation en liaison forte avec **emp** peuvent servir d'attribut de tri, à condition bien sûr d'éviter les correspondances multiples. Nous avons choisi **emp.sal** comme critère de tri primaire, et **emp.ename** comme critère de tri secondaire (figure 3.17). Dans ce champ, les cadres seront triés d'abord selon leur salaire, puis, en cas d'égalité, selon leur nom. Pour le premier, l'ordre descendant a été choisi; pour le second, l'ordre alphabétique a été préféré (ascendant).

Pour ne pas attribuer de rôle multiple à la jointure $J[dept→emp]$ et aux attributs **emp.sal** et **emp.ename**, le champ **chefs** ne peut lui aussi être défini que comme une "fenêtre" de consultation de données de la base (accès en lecture seulement).

### 3.2.3.6 Correspondances des champs "objet de classe inconnue"

Définir la correspondance relationnelle d'un champ destiné à contenir un objet de classe inconnue (objet simple, ensemble d'objets, ensemble ordonné d'objets) n'est pas une chose immédiate. Cette correspondance est en plus vide de sens pour la démarche inverse qui consiste à vouloir associer une représentation objet à des structures relationnelles qu'on souhaite exploiter dans l'environnement. En effet, un attribut qui constitue une référence
vers une autre table permet naturellement de définir une jointure, correspondance d'un champ à objet de classe connue.

Ce type de champ devait pouvoir être rendu persistant car il existe dans les langages objet que nous utilisons et le fait de l'ignorer pouvait rendre l'utilisation de `DRIVER` sans intérêt pour certaines applications.

Nous associons le champ de type "objet de classe inconnue (mais persistante)" à un attribut de type "chaîne de caractères" (figure 3.18) dont les valeurs sont des références codées vers des objets relationnels de la même base. Chaque référence contient le nom de la relation principale de l'objet et la valeur de la clé du n-uplet correspondant.

<table><caption>Figure 3.18 : Associations champ "objet de classe inconnue" - attribut</caption><thead><tr><th>Classe, champ</th><th>Relationattribut,</th><th>Jointures</th></tr></thead><tbody><tr><td>Employé, responsable-de</td><td>emp.responsible_for,</td><td>()</td></tr><tr><td>Département, produits</td><td>deptproducts.pref,</td><td>(J[dept→deptproducts])</td></tr></tbody></table>

Les correspondances de ces types de champ à objet de classe indéterminée (objet simple, ensemble d'objets, ensemble ordonné d'objets) peuvent être rapprochées de celles des types gérant des valeurs atomiques (valeur atomique simple, ensemble de valeurs atomiques, ensemble ordonné de valeurs atomiques) : leur élément de base est associé à un attribut. On leur a défini en plus des filtres en lecture/écriture sur la base (cf. §3.2.5.3) qui permettent de transformer les valeurs. Le premier fonctionne dans le sens base vers couche objet. Il reçoit en entrée la valeur de l'attribut, c'est-à-dire une chaîne de caractères, et rend en sortie la valeur à placer dans le champ de l'objet, à savoir un pointeur sur l'objet contenu. Le second fonctionne dans le sens inverse. Il reçoit en entrée un objet et renvoie en sortie une chaîne de caractères codant la référence relationnelle de l'objet.

Deux champs de notre application sont des champs "objet de classe inconnue".

L'un est le champ `responsable-de` de la classe `Employé`, de type "objet de classe inconnue", référençant un objet dont l'employé est responsable. L'objet en question peut être une instance de `Département`, de `Projet` ou de classes correspondant à des produits. Sa correspondance doit être un attribut d'une relation élémentaire de la classe `Employé`. Définissons un nouvel attribut `responsible_for` de type `string` à la relation `emp` (figure 3.19). Nous l'associons au champ `responsable-de` (figure 3.18). Les valeurs qui y sont contenues et qui résultent de l'écriture d'objets persistants dans la base sont les références codées d'objets relationnels.

Le second est le champ `produits` de la classe `Département`, de type "ensemble d'objets de classe inconnue", qui référence les produits dépendant de chaque département. Encore une fois, ces produits peuvent être instances de classes indépendantes et différentes. Leurs références sont gérées dans l'attribut `pref` de la table `deptproducts` (figures 3.18 et 3.20). Chaque n-uplet de cette table fait état d'un lien entre un produit et un département. Une
<table><caption>Figure 3.19 : Correspondance d'un champ objet2 : la table emp complétée</caption><thead><tr><th colspan="5">Table emp</th></tr><tr><th>empno</th><th>ename</th><th>fname</th><th>...</th><th>responsible_for</th></tr></thead><tbody><tr><td>7839</td><td>king</td><td>paul</td><td>...</td><td>project/102</td></tr><tr><td>7566</td><td>jones</td><td>eric</td><td>...</td><td>dept/20</td></tr><tr><td>7698</td><td>blake</td><td>harold</td><td>...</td><td>dept/30</td></tr><tr><td>7782</td><td>clark</td><td>john</td><td>...</td><td>dept/10</td></tr><tr><td>7902</td><td>ford</td><td>john</td><td>...</td><td>project/101</td></tr><tr><td>7788</td><td>scott</td><td>pit</td><td>...</td><td>project/103</td></tr><tr><td>7499</td><td>allen</td><td>jack</td><td>...</td><td>software/743</td></tr><tr><td>7521</td><td>ward</td><td>peter</td><td>...</td><td>hardware/53</td></tr><tr><td>7654</td><td>martin</td><td>georges</td><td>...</td><td>...</td></tr><tr><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td></tr></tbody></table>

jointure J[dept→deptproducts], qui lie la table deptproducts à la table dept, permet d'identifier ce lien. Son expression est :

$$ \text{dept.deptno} = \text{deptproducts.deptno} $$

Chaque produit pouvant être référencé par plusieurs départements, la jointure J[dept→deptproducts] est l'expression du lien N:M qui lie départements et produits.

<table><caption>Figure 3.20 : La table deptproducts</caption><thead><tr><th colspan="2">Table deptproducts</th></tr><tr><th>deptno</th><th>pref</th></tr></thead><tbody><tr><td>20</td><td>software/743</td></tr><tr><td>30</td><td>software/874</td></tr><tr><td>30</td><td>software/743</td></tr><tr><td>30</td><td>hardware/53</td></tr><tr><td>...</td><td>...</td></tr></tbody></table>

### 3.2.3.7 Correspondances des champs "calculés"

À l'inverse, les champs de type "calculés" ne présentent d'intérêt que pour associer une représentation objet à des données relationnelles. Ils permettent de consulter le résultat d'un calcul sur la base depuis la couche objet. Ils ont naturellement comme correspondance l'expression du calcul sur les attributs (figure 3.21).
Cette expression doit éventuellement être complétée par une chaîne de jointures qui, soit rapproche entre eux les attributs intervenant pour le calcul mono-n-uplet, soit sélectionne les n-uplets pris en compte dans chaque opération pour le calcul sur les ensembles de n-uplets.

Toutes les jointures de la chaîne doivent être élémentaires pour ce qui est du calcul mono-n-uplet, seulement les $N - 1$ premières pour le calcul multi-n-uplets.

<table><caption>Figure 3.21 : Correspondance de champs calculés</caption><thead><tr><th>Classe, champ</th><th>Expression du calcul,</th><th>Jointures</th></tr></thead><tbody><tr><td>Vendeur, sal-total</td><td>(emp.sal + emp.com),</td><td>()</td></tr><tr><td>Département, moy-sal</td><td>Moyenne emp.sal),</td><td>(J[dept→emp])</td></tr></tbody></table>

Notre application comprend deux exemples de champ calculé. L'un est associé à un calcul mono-n-uplet, l'autre à un calcul multi-n-uplets.

Le premier est le champ sal-total de la classe Vendeur. Ce champ va contenir, pour chaque vendeur, la somme (salaire+commission) des attributs emp.sal et emp.com du n-uplet correspondant (figure 3.21). Ces deux attributs se trouvant dans la relation principale, aucune jointure n'est nécessaire pour les rapprocher.

Le second est le champ moy-sal de la classe Département. Ce champ va recueillir, dans chaque département, la moyenne des salaires des employés en faisant partie. Il est associé à l'attribut emp.sal (figure 3.21). Les n-uplets correspondant aux employés de chaque département sont sélectionnés au moyen de la jointure $J[dept→emp]$, déjà décrite en §3.2.3.3 et utilisée pour la correspondance des champs personnel et chefs de la classe Département.

### 3.2.4 Tables physiques et tables logiques

Le langage de description et la représentation objet sous-jacente des correspondances se devaient d'éviter de limiter les possibilités offertes par les définitions qui viennent d'être présentées.

Cela nous a amené à considérer en particulier un cas de champ objet où la jointure qui en est la correspondance est une *auto-jointure*, c'est-à-dire relie une table de départ et une table d'arrivée qui sont les mêmes. Ce cas correspond à un champ objet défini dans la classe C et destiné à recueillir des objets de cette même classe C. La jointure correspondante lie la table principale de la classe C à la table principale de la classe des objets contenus, soit la même table. La description d'une telle jointure s'avère impossible si l'on ne peut pas différencier dans l'expression les attributs de la table de départ et ceux de la table d'arrivée.

Ce type de jointure est descriptible en DRIVER grâce aux notions de *table physique* et
de table logique. Par définition, les tables physiques s'identifient aux tables définies dans la base de données. On peut donc déclarer dans l'environnement une table physique par table accessible dans la base. Associées à une même table physique peuvent être définies autant de tables logiques que nécessaire. Chacune d'entre elles est indépendante des autres, se désigne par un nom différent, mais référence en réalité la même table physique.

Dans l'auto-jointure, la table de départ et la table d'arrivée correspondent à la même table physique. On les différencie en utilisant deux tables logiques différentes. Le niveau logique permet dans la jointure de faire comme si les deux tables jointes étaient différentes et de référencer les n-uplets joints sous des noms différents, comme s'ils étaient contenus dans des tables différentes.

En DRIVER, on met généralement en œuvre des tables logiques.

Un certain nombre de primitives avancées permettent cependant de manipuler spécifiquement des tables physiques, mais leurs noms très explicites précisent sans ambiguïté la nature des objets manipulés.

Autre dérogation à la règle, les notions de relation principale et relation secondaire caractérisent les tables physiques. Quand une table logique participe à la correspondance d'une classe, sa table physique associée est table principale ou table secondaire de la même classe. Elle n'est plus disponible pour être table principale ou secondaire une nouvelle fois, que ce soit par le biais de la même table logique ou d'une autre.

Par ailleurs, la déclaration d'une table par driver-deftable, telles celles présentées en §3.1.3.1, se traduit automatiquement, outre la définition de la table physique, par la définition d'une table logique de même nom. C'est cette table logique qui sera ensuite référencée dans les déclarations à venir.

La correspondance d'une classe est une table bien évidemment logique. Seulement, pour des raisons de simplification, cette table principale ne pourra être qu'une table logique de même nom que la table physique associée.

Quand on définit la correspondance d'un champ, les attributs sélectionnés sont également logiques. Pour chaque jointure, la table de départ, la table d'arrivée, les tables intermédiaires sont des tables logiques. En particulier, une chaîne de jointures relie donc des tables logiques.

<table><caption>Figure 3.22 : Auto-jointure, correspondance de champ objet</caption><thead><tr><th>Classe, champ</th><th>Jointures</th></tr></thead><tbody><tr><td>Employé, chef ←→ (J emp → emp1 (emp))</td><td></td></tr></tbody></table>

Notre application sur les employés comporte un champ dont la correspondance est une
auto-jointure. Il s'agit du champ de type objet chef de la classe Employé (figure 3.23).

Le chef d'un employé est aussi une instance d'Employé. La jointure correspondante est
donc une jointure de la relation emp vers la relation emp. Pour discerner dans la jointure
la table de départ et la table d'arrivée, on rebaptise la seconde emp1 (figure 3.23). Si on

<figure><img src="image_14.png" /><figcaption>Figure 3.23 : Auto-jointure de la table emp vers elle-même</figcaption></figure>

identifie donc la relation des “chefs” comme étant emp1 (table logique définie sur la table physique emp), la jointure J emp → emp1 ( emp )], qui représente dans la base le lien entre un employé et son chef, a pour expression :

$$
\text{emp.mgr} = \text{emp1}(\text{emp}).\text{empno}
$$

L'intérêt des tables logiques et physiques ne se limite pas à permettre l'expression des auto-jointures. Au travers d'un même objet relationnel, ces notions rendent possible le fait d'accéder à des informations contenues dans des n-uplets différents d'une même table secondaire. Il suffit pour cela d'associer aux champs en question, des correspondances sur des tables logiques différentes. Il importe malgré tout que les règles concernant la définition de correspondances multiples soient respectées pour maintenir la cohérence de la base (cf. §3.2.3.1).

3.2.5 Description des correspondances en DRIVER

3.2.5.1 Description de la correspondance d'une classe

Décrire une correspondance consiste en DRIVER à :

• rapprocher une classe principale et une relation principale;

• définir (éventuellement) les jointures dont on aura besoin;

• rapprocher chaque champ de sa correspondance relationnelle.

L'exemple suivant (listing 3) illustre la définition de correspondances entre les rela-
tions emp, person et dept, décrites en §3.1.3.1 (listing 1), et les classes Employé, Vendeur
et Département, décrites pour leur part en 3.1.3.2 (listing 2).
<caption>Listing 3. Description de correspondances en DRIVER</caption>

(driver-defclass-map Employe emp
  (let joins
    ((j1 (emp (p person)
      ((lambda (a1 a2 a3 a4)
        (and (eq a1 a3) (eq a2 a4)))
        (emp ename) (emp fname) (p fname) (p fname))))
    (j2 (emp (emp1 emp)
      ((lambda (a1 a2) (eq a1 a2)))
      (emp mgr) (emp1 empno))))
    (j3 (emp (sg salgrade)
      ((lambda (a1 a2 a3)
        (and (>= a1 a2) (<= a1 a3)))
        (emp sal) (sg losal) (sg hisal)))))
    (j4 (emp dept
      ((lambda (a1 a2) (eq a1 a2)))
      (emp deptn) (dept deptno))))
    (j5 (emp address
      ((lambda (a1 a2) (eq a1 a2)))
      (emp empno) (address empno))))
    (j6 (p vehicle
      ((lambda (a1 a2) (eq a1 a2)))
      (p car) (vehicle vnum)))))
    (fields (nom (emp ename) ()
      (prenom (emp fname) ()
      (num-ss (p ssnum) (j1))
      (qualif (emp job) ()
      (chef () (j2))
      (salaire (emp sal) ()
      (grade (sg grade) (j3) (readonly t))
      (dpt () (j4))
      (adresse () (j5))
      (tel-prive (p phone) (j1))
      (vehicule () (j1 j6)))))
    (driver-defclass-map Vendeur
      (fields (situ-famille (p position) ((Employe emp p)))
      (commission (emp com) ()
      (sal-total ((lambda (a1 a2) (+ a1 a2)))
        (emp sal) (emp com))
      ()))
    (driver-defclass-map Departement dept
      (let joins
        ((j1 (dept (e emp)
          ((lambda (attr1 attr2) (eq attr1 attr2)))
(dept deptno) (e deptn)))
(j2 (dept (ds deptsite)
((lambda (a b) (eq a b))
(dept deptno) (ds dept))))
(j3 (dept (dp deptproducts)
((lambda (a b) (eq a b))
(dept deptno) (dp deptno)))))
 attrconstraint ((lambda (no) (> no 0)) (dept deptno))
())
 fields (nom (dept dname)())
 (produits (dp pref) (j3))
 (sites (ds sname) (j2))
 (chefs () (j1)
 (orders ((e sal) desc)
 ((e ename) asc))
 (readonly t))
 (personnel () (j1) (readonly t))
 (moy-sal ((lambda (a1) (avg a1)) (e sal))
 (j1)))))

Dans cet exemple, la classe **Employé** est associée à la relation **emp** tandis que la classe **Département** est associée à la relation **dept**.

Le nom d'une classe principale est suivie du nom de la table qu'on lui associe. Ce nom de table est inutile à rappeler pour les sous-classes (ou classes secondaires) qui, comme nous l'avons vu, sont associées à la même relation que leur classe principale. C'est la raison pour laquelle la correspondance **emp** de **Vendeur** n'est pas représentée.

L'association classe - relation est suivie des descriptions des correspondances des champs. Ces descriptions sont regroupées en une liste introduite par le mot réservé **fields**. Lorsqu'au moins une des correspondances comprend une jointure, cet environnement **fields** est englobé dans un environnement **letjoins** qui permet de décrire les jointures pour les référencer ensuite dans les correspondances de champs.

Des paramètres optionnels permettent de préciser la correspondance de la classe. Ici encore, une option se présente sous forme d'une liste composée d'au moins deux éléments dont le premier identifie l'option et le ou les suivants donnent son expression ou sa valeur.

* **Classe sans correspondance relationnelle** Il peut être utile de déclarer temporairement une classe sans correspondance, notamment dans la phase de mise-à-point. Cette option est faite pour cela.

Elle est introduite par le mot réservé **mappedp** et sa valeur est **booléenne**.
* **Définition de contrainte d'attribut** Les contraintes sur les champs atomiques d'une classe se traduisent en contraintes sur les attributs correspondants lors du chargement des objets en mémoire. Il est aussi parfois utile d'imposer des contraintes sur des attributs de relations élémentaires de la classe qui ne sont les correspondances d'aucun champ atomique. Cette option permet de le faire. Ces contraintes s'ajoutent aux contraintes de classes et participent également aux opérations de filtrage d'objet lors des transferts de données en mémoire.

Ceci offre la possibilité de déterminer la classe d'un objet relationnel au sein d'une hiérarchie en fonction de la valeur d'un attribut sans correspondance. De façon indirecte, la correspondance d'un tel attribut n'est pas un champ mais une classe : la valeur de l'attribut détermine la classe de l'objet. Une telle utilisation de cette option est faite au sein du méta-schéma (cf. §3.3.2.4).

Cette option est identifiée par le mot réservé **attrconstraint** qui introduit un ou deux arguments. Le premier est l'expression de la contrainte, à nouveau présentée sous forme d'une lambda-expression à un argument qui a valeur de prédicat. Quand on l'applique à une valeur de l'attribut concerné, la contrainte est vérifiée si le résultat est vrai (au sens lisp); elle ne l'est pas dans le cas contraire. La lambda-expression doit être précisée de l'attribut associé à son argument. On la place dans une liste dont elle est le premier élément et où le second est l'attribut correspondant. Si l'attribut n'est pas dans la table élémentaire, une chaîne de jointures élémentaires permettant de l'atteindre doit être précisée. Cette chaîne constitue le deuxième argument facultatif de l'option.

Notre application présente un exemple de contrainte d'attribut. L'attribut **deptno** de la relation **dept** n'a pas de champ atomique associé. Nous souhaitons le contraindre de façon à ne retenir comme objets relationnels que les n-uplets présentant une valeur positive pour cet attribut. Comme l'attribut appartient à la relation principale, aucune jointure n'est nécessaire; c'est pourquoi le deuxième argument du **attrconstraint** vaut ()).

Examinons maintenant comment on décrit une jointure en **DRIVER**. Nous nous intéresserons ensuite aux correspondances de champs.

### 3.2.5.2 Description d'une jointure

Les jointures sont définies au sein de l'environnement **letjoins** qui les rend disponibles localement, au sein de l'environnement **fields** englobé. Le **letjoins** prend deux arguments (figure 3.24) : le premier est une liste de déclarations de variables de jointures, le second est l'environnement **fields** où ces variables sont utilisables. Une déclaration de variable de jointure associe un nom de variable à une description de jointure.
<figure><img src="image_15.png" /><figcaption>Figure 3.24 : L’environnement letjoins</figcaption></figure>

Comme nous l’avons vu, une jointure représente pour nous un lien d’une table vers une autre. Elle se définit par sa table de départ, sa table d’arrivée et son expression.

Le corps de la jointure se décrit sous la forme d’un prédicat à *n* variables où chacune d’entre elles représente un des attributs intervenant dans la jointure. Ce prédicat est présenté sous la forme d’une lambda-expression<fnref n="5" />. Un n-uplet de valeurs des attributs concernés vérifie la jointure si l’application du prédicat à ces valeurs renvoie un résultat vrai (au sens lisp).

La lambda-expression doit être précisée des attributs associés à ses arguments. On la place dans une liste dont elle est le premier élément et où les suivants sont les attributs correspondants.

Prenons un exemple de déclaration de jointure. On a présenté dans la figure 3.9 une jointure liant la table `person` à la table `emp` dont l’expression est :

```
emp.ename = person.pname and emp.fname = person.fname
```

Présentée sous forme d’un prédicat Lisp où les attributs apparaissent par l’intermédiaire de variables, l’expression devient :

```
(lambda (var1 var2 var3 var4)
  (and (eq var1 var3) (eq var2 var4)))
```

où `var1`, `var2`, `var3` et `var4` représentent respectivement `emp.ename`, `emp.fname`, `person.pname` et `person.fname`.

<footnote n="5">⁵ Fonction anonyme et générique en lisp.</footnote>
Si on complète la déclaration en précisant les tables origine et destination et les associations "variable de la lambda" - attribut, on obtient :

```
((lambda (var1 var2 var3 var4)
      (and (eq var1 var3) (eq var2 var4)))
      (emp ename) (emp fname) (person pname) (person fname))
```

On reconnaît ici celle qui a été décrite la première dans le **letjoins** et qui est identifiée par la variable `j1`. Seule subsiste une petite différence. Par concision, nous avons choisi, dans le listing 3, d'utiliser une table logique `p` plutôt que la table logique **person**.

Lors de la première utilisation d'une nouvelle table logique, il convient de préciser à quelle table physique elle est associée en remplaçant son nom par la liste "nom de table logique" - "nom de table physique" correspondante. Cette opération n'est pas nécessaire pour la première utilisation d'une table logique de même nom que la table physique associée.

Pour les tables intermédiaires qui n'apparaissent dans la déclaration qu'au moment de la description des attributs, le principe reste le même. La description d'un tel attribut devient `((new-log-table phys-table) attr)` au lieu de `(log-table attr)`.

À titre d'exemple, nous avons présenté en §3.2.3.3 la jointure `J[project→emp]` qui utilise une table intermédiaire **emproj**. En supposant qu'on souhaite utiliser la table logique **ep** associée à la table physique **emproj** pour exprimer cette jointure, la déclaration de la jointure serait :

```
(project emp
    ((lambda (a1 a2 a3 a4)
        (and (eq a1 a2) (eq a3 a4)))
    (project projno) ((ep emproj) projno) (ep empno) (emp empno))
```

La description des correspondances de la classe **Employé** comprend la définition de l'auto-jointure sur la table **emp** déjà présentée dans la figure 3.23. Cet exemple illustre la manière de décrire les auto-jointures.

Nous avons vu qu'une chaîne de jointures reliait des tables logiques. La correspondance du champ **véhicule** de la classe **Employé** est une chaîne de deux jointures (figure 3.12) qui relie la table **emp** à la table **vehicle** en passant par **person**. En termes logiques, la première jointure établit le lien entre la table **emp** et la table `p(person)`. La seconde doit
alors nécessairement lier la table `p(person)` à la table `vehicle`. Si une autre table logique que `p`, associée à la table physique `person`, y était utilisée, la chaîne serait inconsistante.

Ainsi, au sein d'un **letjoins**, un nom de table logique ne représente qu'une seule et même table logique. De ce fait, une table logique qui a été déclarée dans une jointure n'a pas à l'être à nouveau si elle est utilisée au sein d'une autre jointure. Dans l'application, `p` représente effectivement la même table logique dans les deux déclarations de jointures `j1` et `j6`.

Lors de la description des correspondances d'une sous-classe, il est parfois nécessaire de référencer une jointure définie lors de la description des correspondances d'une classe ancêtre. Cette jointure peut par exemple permettre d'accéder à un attribut d'une table secondaire de cette classe ancêtre.

Dans la description de la correspondance du champ, on remplace alors le nom de la variable —qui n'a plus aucune signification à cet endroit— par une référence explicite à la jointure en question. Cette référence est une liste de trois éléments comprenant successivement le nom de la classe pour la correspondance de laquelle la jointure a été définie, le nom de la table logique origine et enfin le nom de la table logique destination.

Le champ `situ-famille` de la classe `Vendeur` a pour correspondance l'attribut `position` de la relation `person`. La jointure dont on a besoin et qui lie les relations `emp` et `person` a déjà été décrite lors de la description des correspondances de la classe `Employé`. Il s'agit de la jointure référencée par la variable `j1` et qui joint la table logique `emp` à la table logique `p`. La référence explicite à la jointure en question est donc (**Employé emp p**). Bien évidemment, l'attribut logique correspondance du champ est `pppérition` (cf. §3.2.5.3).

### 3.2.5.3 Description des correspondances de champs

Quand nous avons établi la correspondance de chaque type de champ (cf. §3.2.3), nous avons associé chacun d'entre eux à un attribut (un calcul sur attributs pour les champs calculés), une ou plusieurs jointures ou aux deux. La syntaxe que nous avons choisie présente la correspondance de tout champ comme étant un couple attribut (ou calcul) - ensemble de jointures. Si l'un de ces deux éléments n'a pas de sens ou n'existe pas pour le champ considéré, on représente son absence par le symbole `()`.

Les correspondances de champ sont décrites au sein de l'environnement **fields**. Chacune est introduite par le nom du champ auquel on s'intéresse. Suivent les éventuels attribut (ou calcul d'attributs) et liste de jointures associés, complétés, selon les besoins, d'informations optionnelles.

Pour la plupart des types de champ, on déduit facilement la syntaxe à utiliser à partir des informations ci-dessus et des correspondances établies en §3.2.3.

Précisons seulement, pour les cas où la correspondance est un attribut, que cet attribut
doit appartenir à la table logique finalement atteinte par la chaîne de jointures associée.

Par exemple, le champ `num-ss` de la classe `Employé` est associé à l'attribut `ssnum` de la relation `person`. La jointure permettant d'atteindre cet attribut lie la table logique `emp` à la table logique `p`. De ce fait, la correspondance du champ est nécessairement `p.ssnum`.

Quand la référence de la jointure finale de la chaîne est une référence explicite, le principe reste le même. L'attribut correspondance du champ doit appartenir à la table logique destination de cette jointure. On l'a vu avec la description de la correspondance du champ `situ-famille` de la classe `Vendeur`.

Examinons plus en détail la description de la correspondance des champs "calculés" où l'expression d'un calcul sur la base se substitue à l'attribut.

Dans notre syntaxe, cette expression doit être présentée sous forme d'une lambda-expression dont les arguments représentent à nouveau les attributs intervenant dans le calcul. Comme pour la description des jointures, la lambda-expression doit être précisée des attributs associés aux arguments. On la place dans une liste dont elle est le premier élément et où les suivants sont les attributs correspondants.

Le calcul fait a priori intervenir des attributs de type numérique. Différents opérateurs sont disponibles, selon que le calcul est mono-n-uplet ou multi-n-uplets.

Pour un calcul mono-n-uplet, les quatre opérations de base (`add`, `soustraction`, `sub`, `multiplication`, `mul`, `division`) peuvent être utilisées.

L'expression d'un calcul multi-n-uplets fait intervenir des opérateurs ensemblistes. Deux sont disponibles :

* Le premier `sum` permet d'effectuer la somme d'un certain nombre de valeurs d'un attribut, sélectionnées par `jointure(s)`;

* Le second `average` (ou `avg`) permet d'effectuer la moyenne d'un certain nombre de valeurs d'un attribut, sélectionnées elles aussi au moyen d'une chaîne de jointures.

Deux exemples de notre application illustrent ces deux types de calcul. Les champs correspondant sont `sal-total` de la classe `Vendeur` et `moy-sal` de la classe `Département`. Leurs deux correspondances ont été décrites en §3.2.3.7.

## Les paramètres optionnels de correspondance de champ

Les déclarations des correspondances de champs peuvent être complétées à l'aide des paramètres optionnels suivants :

* **Champ à accès en lecture seulement à la base de données.** On peut ou doit parfois préciser qu'un champ n'est qu'une "fenêtre" de consultation de données de la base. Comme nous l'avons déjà vu, ceci est nécessaire quand un attribut participe à plusieurs correspondances et évite d'avoir des problèmes de cohérence des données persistantes.
Cette option est identifiée par le mot réservé **readonly**. La valeur par défaut est faux (champ à accès en lecture et écriture dans la base), excepté pour les champs de type calculés qui sont de toute façon "readonly", quelle que soit la valeur éventuellement donnée à ce booléen.

Cette option est illustrée, dans notre application, au sein des correspondances des classes **Employé** et **Département**. Le champ **grade** de la classe **Employé** permet de consulter dans la table **salgrade** quel grade correspond à chaque salaire. Si ce champ n'était pas "readonly", la modification d'un grade d'un objet entraînerait lors du report dans la base une incohérence dans cette table : le nouveau grade remplacerait l'ancien pour la plage de salaire correspondante.

Le champ **personnel** de la classe **Département** est déclaré "accès en lecture seulement" car sa correspondance est une jointure objet déjà utilisée pour le champ **chefs**. Elle est également utilisée par le champ **moy-sal**, mais ce dernier est un champ calculé qui est "readonly" à fortiori.

* **Définition de filtres de champs**
  Des filtres peuvent être mis en place entre un champ et sa correspondance relationnelle. Toute valeur issue de la base et à affecter au champ ou encore contenue dans le champ et à reporter dans la base peut être modifiée par l'intermédiaire de tels filtres de façon à lui donner une forme adaptée à l'usage qu'on souhaite en faire.

Deux filtres peuvent être créés par champ. L'un reçoit en entrée la valeur issue de la base de données et doit renvoyer la valeur à affecter dans le champ de l'objet. Le second reçoit en entrée la valeur issue du champ et doit renvoyer la valeur à transmettre au monde relationnel.

Ces filtres sont mis en place sous forme de méthodes qui sont invoquées lors des transferts de données.

Les méthodes **driverloading** sont invoquées pour filtrer une donnée issue de la base et destinée à être placée dans le champ d'un objet. Inversement, les méthodes **dri-**verwriting sont invoquées pour filtrer un contenu de champ à reporter dans la base de données.

Une paire de filtres est déclarée pour le champ correspondant en utilisant cette option, introduite par le mot réservé **filter**. Ce mot réservé doit être suivi du nom donné à la paire de filtres, plus des mots clés précisant certains arguments à passer à ces filtres. La syntaxe de cette option est :

```
(filter <filter-name>)
    ou
(filter (<filter-name> [class] [field] [object]))
```
Les crochets [ ] indiquent que le mot clé qu'ils encadrent est optionnel. L'utilisation de l'option *filter* nécessite la définition par l'utilisateur des deux méthodes de filtrages de noms

```python
#:driverloading:<filter-name>
et
#:driverwriting:<filter-name>
```

qui assureront le filtrage des valeurs entre le champ correspondant et la base de données.

Si le mot clé *filter* introduit le seul symbole `<filter-name>`, les méthodes seront invoquées avec comme unique argument la valeur à filtrer. Si, au contraire, il introduit plusieurs paramètres, le premier doit être le nom du filtre `<filter-name>` suivi d'un ou plusieurs mots clés parmi *class*, *field* et *object*. Dans ce cas, les méthodes seront invoquées avec comme premier argument la valeur à filtrer, puis, selon les mots clés choisis, l'objet *DRIVER* qui représente la classe de l'instance à laquelle appartient le champ, l'objet *DRIVER* qui représente le champ concerné, ou encore l'instance concernée elle-même. Le passage de ces différents objets avec la valeur à filtrer permet de tenir compte du contenu de certains de leurs champs pour déterminer la valeur renvoyée par le filtre.

Il est possible en *DRIVER* de choisir dans quel ordre vont être chargés les champs d'un objet (voir fonction *DRIVER-FIELD-LOADING-ORDER* en annexe). Un intérêt de pouvoir choisir cet ordre peut être d'utiliser le contenu de certains champs de l'objet en cours de chargement – champs à charger en priorité donc – pour en construire d'autres.

Un certain nombre de types de champ atomiques ont des filtres qui leur sont associés par défaut. Ces types sont *symbol*, *float*, *fix*, *integer* et *string*. Il n'est donc pas nécessaire de définir des filtres par exemple pour un champ atomique de type *symbol* associé à un attribut relationnel de type *string* puisqu'il en existe par défaut. Bien entendu, il est tout-à-fait possible de le faire si c'est nécessaire.

À titre d'exemple, le jeu de méthodes défini pour filtrer un champ atomique de type *symbol* est présenté dans le listing 4.

Nous avons vu en §3.2.3.6 qu'on pouvait rapprocher les correspondances des types de champ à objet de classe indéterminée des types gérant des valeurs atomiques. Le jeu de méthodes de filtrage est lui aussi donné pour exemple dans le listing 4.

<caption>Listing 4. Méthodes de filtrage de champs</caption>

```python
(de #:driverloading:symbol-filter (str)
    ;; in: string out: symbol
```
(symbol () str))

(de #:driverwriting:symbol-filter (symbol)
      ;; in: symbol out: string
      (string symbol))

(de #:driverloading:compressed-object-reference (str)
      ;; in: string out: object
      (driver-get-compressed-key-object (symbol () str)))

(de #:driverwriting:compressed-object-reference (obj)
      ;; in: object out: string
      (string (driver-object-compressed-key obj))))

* **Champ sans correspondance relationnelle** Il est possible de définir des champs comme étant sans correspondance relationnelle. Comme l'option de même nom pour les correspondances de classe, elle permet de déclarer un champ sans correspondance. Elle est notamment utile quand la correspondance du champ a été décrite et quand on souhaite temporairement ne pas en tenir compte.

Elle est introduite par le mot réservé `mappedp` et sa valeur est booléenne.

* **Choix de critères de tri pour les champs "ensemble ordonné"** Nous avons vu en §3.2.3.5 que les éléments d'ensemble sont ordonnés d'après les valeurs d'attributs de tables utilisées dans la jointure et en liaison forte avec la table destination. Le premier attribut définit l'ordre primaire, le second l'ordre secondaire, et ainsi de suite.

L'option est introduite par le mot réservé `orders`. Le ou les attributs doivent être précisés avec, pour chacun d'entre eux, l'ordre ascendant ou descendant (mots clés `asc` et `desc`) dans lequel doit se faire le tri.

Notre application présente un exemple de champ de type "ensemble ordonné". Ce champ est le champ `chefs` de la classe `Département`. Il est destiné, pour chaque département, à recueillir ses dirigeants, ordonnés par ordre de salaire décroissant, puis, pour les gens ayant même salaire, selon leurs noms par ordre alphabétique.
## 3.3 Représentation interne des correspondances

### 3.3.1 Généralités

La description en *DRIVER* des structures de données relationnelles, des classes et des correspondances se traduit en interne au logiciel par la création d'objets système qui représentent ces informations. Ces objets sont instances de classes qui décrivent chacune un concept ou un type de correspondance.

Les schémas de correspondances sont eux-mêmes des objets, instances de la classe *Driver-mapping*, que les déclarations successives permettent de construire incrémentalement.

Il est possible de leur apporter la persistance car leurs classes sont décrites au sein d'un *méta-schéma de correspondances driver-metamapping* où leur sont associées des relations système.

Nous allons étudier la représentation des correspondances et nous intéresser aux différentes classes système définies en *DRIVER* en examinant le méta-schéma. D'une part, ce dernier comprend la description de la plupart d'entre elles. Enfin, c'est là l'occasion de découvrir un schéma de correspondances complet exploitant une bonne partie des fonctionnalités offertes par le logiciel.

### 3.3.2 Le méta-schéma de correspondances

#### 3.3.2.1 Les schémas de correspondances

En *DRIVER*, les schémas de correspondances sont des objets de la classe *Driver-mapping*. Ils sont identifiés par le nom qu'on leur donne à la création. La notion de *schéma courant* permet de savoir à tout moment à quel schéma s'appliquent les déclarations de tables, de classes et de correspondances qui peuvent être faites. Un schéma courant est nécessairement défini à partir du moment où un au moins a été créé. Il ne peut y avoir qu'un schéma courant à un moment donné.

Au sein des objets schémas, la description des classes et de leurs correspondances est accessible par l'intermédiaire du champ *classes* (listing 5).

Les structures relationnelles sont accessibles via différents champs selon qu'on souhaite atteindre des tables ou des attributs, logiques ou physiques (champs *defattributes*, *deftables*, *attributes* et *tables*). Ces champs sont principalement utilisés pour retrouver des objets pendant la phase de construction du schéma. De la même façon, le champ *joins* permet d'accéder rapidement à une jointure définie dans le schéma sans devoir nécessairement explorer les correspondances des différents champs.

Un autre intérêt de ces champs est qu'ils permettent un chargement plus rapide du schéma stocké dans la base de données relationnelles (cf. §4.1.3.1) en augmentant sensiblement le nombre de liens directs existant entre le chargeur<fnref n="6" /> (le schéma) et les objets

<footnote n="6">⁶Voir définition en 4.1.3.3.</footnote>
qu'il référence. Le coût en requêtes du chargement est également considérablement réduit (cf. §4.1.3.3).

<caption>Listing 5. La classe `Driver-mapping` et sa correspondance</caption>

```
 driver-defclass Driver-mapping ()
 (name          atom          symbol)
 (classes       ordobjset      Driver-class)
 (defattributes  objectset      Driver-defattribute)
 (deftables      objectset      Driver-deftable)
 (attributes     objectset      Driver-attribute)
 (tables         objectset      Driver-table)
 (joins          objectset      Driver-join))

 driver-deftable db_mapping
 (mapnum        integer 2  keypart)
 (mapname       string 20  (
 ))

 driver-defclass-map Driver-mapping db_mapping
 (letjoins
  ((j1 (db_mapping db_class
        ((lambda (a1 a2) (eq a1 a2))
          (db_mapping mapnum) (db_class mapping)))))
  (j2 (db_mapping db_defattribute
        ((lambda (a1 a2) (eq a1 a2))
          (db_mapping mapnum) (db_defattribute mapping)))))
  (j3 (db_mapping db_deftable
        ((lambda (a1 a2) (eq a1 a2))
          (db_mapping mapnum) (db_deftable mapping)))))
  (j4 (db_mapping db_attribute
        ((lambda (a1 a2) (eq a1 a2))
          (db_mapping mapnum) (db_attribute mapping)))))
  (j5 (db_mapping db_table
        ((lambda (a1 a2) (eq a1 a2))
          (db_mapping mapnum) (db_table mapping)))))
  (j6 (db_mapping db_join0
        ((lambda (a1 a2) (eq a1 a2))
          (db_mapping mapnum) (db_join0 mapping)))))
 (fields (name          (db_mapping mapname) (
        (classes          ()              (j1)
          (orders ((db_class c_ord) asc)))
        (defattributes  ()              (j2))
        (deftables       ()              (j3))
        (attributes      ()              (j4))
        (tables          ()              (j5))
        (joins           ()              (j6)))))
```
Un autre champ, absent dans la déclaration présentée car non persistant, est un dic-
tionnaire d'accès aux objets persistants, instances des classes dont la correspondance est
décrite dans ce schéma. Ce dictionnaire permet de retrouver un objet en connaissant sa
référence relationnelle.

Enfin, un autre encore contient des objets système `DRIVER` permettant de gérer la suppression d'objet (cf. §4.3). Ces objets sont utiles pendant les validations de transaction pour déclencher la suppression physique des objets dans la base et des références système aux objets détruits en mémoire, en particulier leur référençage ou le référençage de leurs clés dans les dictionnaires.

La correspondance de la classe `Driver-mapping` est une relation de nom `db_mapping` qui ne comprend que deux attributs. Le premier contient le numéro des schémas et le second, leurs noms. La plupart des champs de la classe `Driver-mapping` étant de type "ensemble d'objets", leurs correspondances sont des jointures. Ces jointures —des égalités entre deux attributs— ne font qu'affirmer à chaque fois la sémantique des attributs `mapping` des relations principales jointes, à savoir, être des références à la clé `mapnum` de la relation `db_mapping`.

Enfin, on notera que l'ordre des objets est important dans le champ `classes`. Cet ordre est celui dans lequel interviennent les différentes classes lors du chargement des objets jumeaux en mémoire (cf. §4.1.2.2). L'ordre est déterminé d'après les valeurs de l'attribut `c_ord` de la relation `db_class`, classées par ordre croissant.

### 3.3.2.2 Les tables et attributs physiques et logiques

Les tables physiques et les tables logiques sont respectivement représentées par des inst-
tances des classes `Driver-deftable` et `Driver-table`, les attributs physiques et les attri-
buts logiques, par des instances des classes `Driver-defattribute` et `Driver-attribute`.
Ces quatre classes sont présentées dans le listing 6.

Une table physique comprend un nom et une clé formée d'attributs (champs *name* et *key*). Le nom est unique si bien qu'une table physique peut être retrouvée par son nom. Dans la version actuelle de `DRIVER`, aucun champ ne renseigne sur les attributs qui la composent car le système n'a à aucun moment besoin de cette information pendant son exécution. Par ailleurs, la primitive `driver-table-attributes` renvoie ce résultat pour les besoins de l'utilisateur.

On peut s'étonner que le champ *key* référence des attributs logiques plutôt que des attributs physiques. Cet état de fait a pour seule raison l'amélioration des performances qu'il apporte dans certaines routines de construction de requêtes. Cette efficacité accrue est même en réalité la seule chose qui justifie l'existence de ce champ.

On peut ajouter que toute création de table physique s'accompagne automatiquement de la création de la table logique de même nom et que toute création d'attribut physique s'accompagne aussi de la création de l'attribut logique de même nom. Ainsi, lors de la
définition d'un nouvel attribut faisant partie de la clé, l'ajout d'un attribut logique dans ce champ **key** ne pose aucun problème. Cet attribut est de toute façon créé en même temps que son homologue physique et est donc disponible.

<caption>Listing 6. Les classes de représentation relationnelle</caption>

(driver-defclass Driver-deftable ()
(name atom symbol)
(key objectset Driver-attribute))

(drive -defclass Driver-defattribute ()
(deftable object Driver-deftable)
(attrname atom symbol)
(status atom symbol)
(type atom symbol)
 typelen atom float))

(drive -defclass Driver-table ()
(deftable object Driver-deftable)
 alias atom symbol))

(drive -defclass Driver-attribute ()
(table object Driver-table)
(defattribute object Driver-defattribute))

Contrairement à la table physique qui ne connaît pas ses attributs, l'attribut physique, lui, possède, par l'intermédiaire de son champ **deftable**, un lien sur la table dont il fait partie. Il a également un nom (**attrname**), un type et sa longueur (**champs type** et **typelen**), un statut (**champ status**) qui indique s'il est soumis à contrainte (**unique**, **not-null**) ou s'il fait partie de la clé (**keypart**). On le retrouve par le nom de sa table et son nom propre.

Les tables logiques sont des références logiques de tables physiques. On a vu qu'elles permettaient entre autres la manipulation de plusieurs n-uplets d'une même table au sein de la même expression (filtre, jointure, requête). De façon naturelle, elles ont un nom (**champ alias**) et un lien sur leur table physique (**champ deftable**). On retrouve aussi une table logique par son nom qui est unique.

Le système crée automatiquement les tables logiques de même nom que les tables physiques. Dans ces objets, le champ **alias** vaut simplement (**). DRIVER** retrouve leurs noms au besoin en allant consulter le nom de la table physique référencée dans le champ **deftable**.

Un attribut logique est un attribut de table logique. Il est composé d'un lien sur une table logique (**champ table**) et d'un lien sur un attribut physique (**champ defattribute**).
Il est identifié par les noms de sa table logique et de son attribut physique associés.

<caption>Listing 7. Correspondances des classes de représentation relationnelle</caption>

(driver-deftable db_deftable
(dtnum integer 2 keypart)
 mapping integer 2 ())
(dtname string 2 ()))

 driver-deftable db_defattribute
(danum integer 2 keypart)
 mapping integer 2 ())
(daname string 2 ())
(deftable integer 2 ())
(status string 20 ())
(type string 20 ())
 typelen float 4 ()))

 (driver-deftable db_table
(tnum integer 2 keypart)
 mapping integer 2 ())
(deftable integer 2 ())
talias string 20 ()))

 (driver-deftable db_attribute
(anum integer 2 keypart)
 mapping integer 2 ())
(table_ integer 2 ())
dattr integer 2 ()))

 (driver-defclass-map Driver-deftable db_deftable
(letjoins
((j1 (db_deftable (attr db_attribute)
((lambda (a1 a2 a3 a4 a5 a6 a7 a8 a9)
(and (eq a1 a2) (eq a3 'keypart)
(eq a4 a2) (null a5)
(eq a6 a7) (eq a8 a9))))
((da db_defattribute) deftable) (db_deftable dtnum)
 da status) (db_table deftable) (db_table talias)
 (attr dattr) (da danum) (attr table_) (db_table tnum)))))
 (fields (name (db_deftable dtname) ())
(key ( ) (j1) (readonly t)))))

 (driver-defclass-map Driver-defattribute db_defattribute
(letjoins ((j1 (db_defattribute (dt db_deftable)
((lambda (a1 a2) (eq a1 a2))
(db_defattribute deftable) (dt dtnum)))))
 (fields (deftable () (j1))
 (attrname (db_defattribute daname) ()))
(status (db_defattribute status) ())
(type (db_defattribute type) ())
 typelen (db_defattribute typelen) ())))

 driver-defclass-map Driver-table db_table
 (letjoins ((j1 (db_table (dt db_deftable))
 ((lambda (a1 a2) (eq a1 a2))
 (db_table deftable) (dt dtnum))))
 (fields (deftable () (j1))
 alias (db_table talias) ())))

 driver-defclass-map Driver-attribute db_attribute
 (letjoins ((j1 (db_attribute db_table)
 ((lambda (a1 a2) (eq a1 a2))
 (db_attribute table_) (db_table tnum))))
 (j2 (db_attribute (da db_defattribute))
 ((lambda (a1 a2) (eq a1 a2))
 (db_attribute dattr) (da danum))))
 (fields (table () (j1))
 (defattribute () (j2))))

Les correspondances définies pour les champs de ces classes sont particulièrement simples pour la plupart, des attributs de tables principales et des jointures entre deux attributs.

Seule la jointure correspondance du champ key de la classe Driver-deftable est un peu complexe. Elle fait intervenir neuf attributs appartenant à quatre tables différentes. Elle lie la table des attributs logiques à celle des tables physiques. Son but est de sélectionner, pour chaque table physique, les attributs logiques élément de clé de même nom que leurs attributs physiques.

À partir de la table physique, on sélectionne les attributs physiques éléments de clé par la jointure A :

db_defattribute.deftable = db_deftable.dtnum
and db_defattribute.status = ’keypart’

Toujours à partir de la table physique, on sélectionne la table logique de même nom
par la jointure B :

db_table.deftable = db_deftable.dtnum
and db_table.talias is null
Enfin, on identifie les attributs logiques en indiquant qu'ils sont associés aux attributs physiques filtrés par la jointure *A* et qu'ils ont pour table logique celle sélectionnée par la jointure *B*. Ceci définit la jointure *C* :

$$ 
\begin{array}{l}
db\_attribute.dattr = db\_defattribute.danum \\
\text{and } db\_attribute.table_ = db\_table.tnum
\end{array}
$$

L'association de ces trois jointures *A*, *B* et *C* permet d'identifier les attributs logiques souhaités pour chaque table physique.

On notera que beaucoup d'attributs intervenant dans cette jointure ont déjà une correspondance dans le schéma. Pour que cette définition de jointure soit acceptée par DRI-VER, on est tenu de déclarer le champ comme "read only". Le fait que ce champ ne puisse pas être écrit dans la base n'a pas d'importance puisque la seule chose qui conditionne l'appartenance d'un attribut à la clé d'une table est la valeur de son champ statut qui doit valoir keypart pour que ce soit le cas.

### 3.3.2.3 Classes et correspondances de classes

Les classes et leurs correspondances sont décrites dans le schéma par les objets de la classe Driver-class (listing 8). Ces objets servent en principe à décrire la correspondance des classes. Cependant, leur rôle les amène à également contenir la description des classes elles-mêmes.

Un objet (correspondance de) classe possède un nom (champ *name* qui l'identifie de façon unique.

L'éventuelle classe mère peut elle-même être décrite en `DRIVER`, auquel cas cette description (également instance de `Driver-class`) est référencée dans le champ `super`. On voit ici que l'implémentation actuelle du système ne prévoit que l'héritage simple. Prendre en compte l'héritage multiple ne se traduirait, au niveau de la représentation des connaissances de `DRIVER`, que par la transformation du type du champ `super` de `object` en `ordobjset`.

<caption>Listing 8. La classe `Driver-class` et sa correspondance</caption>

```
(driver-defclass Driver-class ()
(name         atom         symbol)
(table        object       Driver-table)
 fields      ordobjset    Driver-field)
(restrictionp atom         symbol)
(restrictions objectset  Driver-restriction)
(super        object       Driver-class)
```
(mappedp atom symbol))

 driver-deftable db_class
 (cnum integer 2 keypart)
 (cname string 20())
 (mapping integer 2())
 (c_ord integer 2())
 (table_ integer 2())
 (restrip string 1())
 (super integer 2())
 (mappedp string 1())

 driver-defclass-map Driver-class db_class
 (letjoins ((j1 (db_class db_table
 ((lambda (a1 a2) (eq a1 a2))
 (db_class table_) (db_table tnum))))
 (j2 (db_class db_field
 ((lambda (a1 a2) (eq a1 a2))
 (db_class cnum) (db_field class))))
 (j3 (db_class db_join0
 ((lambda (a1 a2) (eq a1 a2))
 (db_class cnum) (db_join0 class))))
 (j4 (db_class (mother db_class)
 ((lambda (a1 a2) (eq a1 a2))
 (db_class super) (mother cnum))))
 (fields (name (db_class cname)())
 (table () (j1))
 (fields () (j2)
 (orders ((db_field f_ord) asc)))
 (restrictionp (db_class restrip)())
 (restrictions () (j3)
 (filter cl-restrictions))
 (super () (j4))
 (mappedp (db_class mappedp)()))))

Les contraintes imposées à la classe sont contenues dans le champ restrictions.
Ce champ est en fait géré comme un index où sont placés des couples clé-valeur. Les
valeurs jouent le rôle de contraintes et les clés qui permettent d'y accéder sont les objets
contraints. Ce sont soit des champs, soit des attributs généralement sans correspondance.
Une contrainte posée sur un champ dans une classe ancêtre peut être redéfinie. Dans ce
cas, la clé est bien sûr l'objet champ de la classe ancêtre.

Quand la classe est contrainte, le champ restrictionp, qui est un booléen, a une
valeur "vraie". On peut faire la remarque que le champ restrictions peut être vide, et
restrictionp à "vrai". Cela arrive quand aucune contrainte n'a été définie spécifiquement pour la classe, mais qu'au moins une autre existe au niveau d'une classe ancêtre. La
classe en hérite alors naturellement.
On a vu que la correspondance d'une classe était une table logique (cf. §3.2.2 et §3.2.4). Cette table est conservée dans le champ **table**. Elle est nécessairement la même que celle de la table principale. Dans l'implémentation actuelle du système, dans un but de simplification, cette correspondance est forcément une table logique dont le nom est identique à celui de sa table physique associée.

Les correspondances des champs sont contenues dans le champ **fields**. L'ordre dans lequel ces objets sont placés détermine celui dans lequel les champs vont être chargés de leurs valeurs à la construction des jumeaux.

Le champ **mapped** permet de déclarer provisoirement une classe comme étant sans correspondance, sans devoir mettre à vide les champs **table** et **fields**. Il suffit en effet pour cela de simplement mettre à "faux" ce champ dont la valeur est généralement "vraie". Une nouvelle inversion de la valeur de ce booléen rétablit la persistance de la classe.

Enfin, **Driver-class** comprend quatre autres champs, absents dans la déclaration présentée car non persistants.

Le premier n'est utilisé que dans les classes principales. Il contient pour ces classes une table d'accès aux identificateurs relationnels des objets persistants (cf. §3.2.2). La clé d'un identificateur est l'objet persistant lui-même.

Le second contient une version "compilée" des contraintes de la classe et des jointures qui permettent d'atteindre les attributs contraints.

Les deux autres référencent respectivement un objet **Driver-select** (cf. §4.1.4) et un objet **Driver-class-saving** (cf. §4.2.3.2) dès qu'ils ont été construits. Ces deux objets contiennent des informations relatives au chargement et à l'insertion des instances de la classe.

La correspondance ne présente aucune particularité si ce n'est le filtre défini pour le champ **restrictions**. On a vu que le contenu de ce champ n'était pas directement l'ensemble des contraintes appliquées à la classe, mais un index permettant de les manipuler facilement. Un filtre est donc nécessaire pour transformer les ensembles de contraintes lus dans la base en index avant affectation dans le champ et inversement, les index en simples ensembles de contraintes avant écriture dans la base.

### 3.3.2.4 Champs et correspondances de champs

Les champs et leurs correspondances sont décrits par les objets de classe principale **Driver-field** (listing 9). De façon similaire aux classes, ces objets servent à décrire la correspondance des champs. Cependant, leur rôle les amène à également contenir la description des champs eux-mêmes.

Toute une hiérarchie de classes de champ a été définie sous **Driver-field** (figure 3.25) et permet de représenter les correspondances spécifiques à chaque type de champ.

**Driver-field** n'est pas la classe d'un type particulier de correspondance de champ
<figure><img src="image_16.png" /><figcaption>Figure 3.25 : Hiérarchie des classes de champ</figcaption></figure>

et aucun objet ne peut en être précisément instance. Ses champs décrivent simplement les informations communes à tous les types.

Les objets (correspondance de) champ ont un nom (champ *name*) —qui est le nom du champ— et qui les identifie de façon unique au sein de leur classe. Cette dernière est référencée dans *ownerclass*.

La plupart d'entre eux, en fait tous sauf les champs à objet de classe connue (*Object-field*, *Object-set* et *Ordered-object-set*), contiennent des valeurs atomiques ou des ensembles de valeurs atomiques, telles des entiers, des réels, des symboles ou des chaînes de caractères. Ce sous-type, quand il existe, est alors contenu dans *ftype*.

Il est toujours possible de définir un filtre pour un champ, quel que soit son type. Un filtre en comprend en réalité deux. La ou les valeurs lues dans la base transitent par l'un avant leur affectation dans le champ tandis que le contenu du champ transite par le second avant d'être reporté dans la base. Ce doublet est identifié par un nom (cf. §3.2.5.3). Le système a connaissance qu'un filtre a été défini sur un champ quand le champ *filter* de l'objet correspondant contient précisément ce nom.

Nous avons vu qu'il était possible de préciser une valeur initiale pour un champ. Le champ *init-val* contient alors l'expression (au sens Lisp) qui est évaluée à chaque création d'objet. C'est le résultat de cette évaluation qui est aussitôt affecté au champ correspondant.

<caption>Listing 9. La classe *Driver-field* et sa correspondance</caption>

(driver-defclass Driver-field ()
(name atom symbol)
 ownerclass object Driver-class)
(ftype atom symbol)
(filter atom symbol)
(init-val atom бы)
(mappedp atom symbol)
(read-only atom symbol)
(localp atom symbol)
(joins objectset Driver-join))

(driver-deftable db_field
(fnum integer 2 keypart)
(fname string 20 ))
(classfield string 20 ))
(type string 20 ))
(class integer 2 ))
(f_ord integer 2 ))
(filter string 20 ))
(init_val string 50 ))
(mappedp string 1 ))
(r_only string 1 ))
(localp string 1 ))
(attr integer 2 ))
(objclass integer 2 ))
(a string 200 ))
(groupedp string 1 )))

(driver-deftable db_fieldjoins
(field integer 2 keypart)
(join integer 2 keypart)))

(driver-defclass-map Driver-field db_field
 letjoins ((j1 (db_field db_class
 ((lambda (a1 a2) (eq a1 a2))
 (db_field class) (db_class cnum))))
 (j2 (db_field db_join0
 ((lambda (a1 a2 a3 a4) (and (eq a1 a2) (eq a3 a4)))
 (db_field fnum) (db_fieldjoins field)
 (db_fieldjoins join) (db_join0 jnum)))))
 (fields (name (db_field fname) ())
 (ownerclass () (j1)
 (ftype (db_field type) ())
 (filter (db_field filter) ())
 (init-val (db_field init_val) ())
 (mappedp (db_field mappedp) ())
 (read-only (db_field r_only) ())
 (localp (db_field localp) ())
 (joins () (j2)))))

Comme pour les classes, mappedp précise si le champ a une correspondance, ou non.
La valeur de mappedp est généralement "vrai". Elle peut aussi être provisoirement mise à
"faux" pour des champs dont on a pourtant décrit la correspondance mais dont on sou-
haite momentanément "débrancher" la persistance. Ce peut être intéressant, par exemple,
pendant la phase de mise-au-point du schéma.

On a vu qu'on pouvait décrire en `DRIVER` au niveau d'une classe donnée la correspondance d'un champ hérité d'une classe ancêtre (cf. §3.1.3.2). En général, cette classe ancêtre n'est pas décrite dans le schéma de correspondances et n'existe que dans le modèle objet client. Lors de la description de la classe, on indique que le champ déclaré n'est pas un de ses champs propres en positionnant la valeur du champ `localp` à "faux". Ce champ, qui a valeur de booléen comme le précédent, vaut donc "vrai" dans la plupart des cas.

Enfin, `joins` contient la chaîne de jointures participant à la correspondance du champ. Il n'aura pas échappé au lecteur que tous les types de champ peuvent en effet avoir recours aux jointures pour atteindre un ou plusieurs attributs ou pour sélectionner un ensemble de valeurs pertinentes.

La hiérarchie des types de champ est un exemple de hiérarchie de classes associée à une relation où le critère qui détermine la classe de chaque objet relationnel est la valeur d'un attribut sans correspondance. Nous avons même, dans le cas présent, isomorphisme entre les valeurs possibles de l'attribut et les classes de la hiérarchie des champs.

Un n-uplet de la relation `db_field` détermine la classe précise de son objet relationnel associé en fonction de la valeur de l'attribut `classfield`. Chaque valeur possible de l'attribut correspond à un type de champ. La correspondance de la classe `Driver-field` elle-même n'est pas contrainte parce qu'aucun n-uplet de la relation `db_field` n'est à rejeter.

Une jointure peut servir à définir la correspondance de plusieurs champs et une correspondance peut elle-même faire intervenir plusieurs jointures. Le lien qui unit les objets champ et les objets jointure est donc de type N:M. Comme nous l'avons vu, une table indépendante des tables élémentaires de chaque classe est alors nécessaire pour gérer ce lien. Cette table est la table `db_fieldjoins`. La jointure correspondance du champ `joins` utilise le fait que les deux attributs de cette table sont des références aux clés des relations `db_field` et `db_join0`.

## Le champ atomique

Les champs atomiques "classiques" sont représentés par des instances de la classe `Atom-field`. Ce sont ceux que nous désignons sous le terme `atom` lors de leurs déclarations. Ils ont pour correspondance un attribut logique, qui est recueilli dans `attr`. Nous avons vu que l'éventuelle chaîne de jointures permettant d'atteindre l'attribut était contenue dans `joins` qui est local à la classe `Db_field`.

La correspondance de cette classe de champ n'a rien de particulier. On remarquera simplement l'expression de la contrainte imposée à l'attribut `classfield` pour la classe `Atom-field`. On constate aussi ici que `Object2-field` est une sous-classe de `Atom-field`.
<caption>Listing 10. La classe Atom-field et sa correspondance</caption>

(driver-defclass Atom-field Driver-field
 (attr object Driver-attribute))

(driver-defclass-map Atom-field db_field
 (attrconstraint ((lambda (attr) (memq attr '(atom object2))))
 (db_field classfield)))))

(letjoins ((j1 (db_field db_attribute
 ((lambda (a1 a2) (eq a1 a2)))
 (db_field attr) (db_attribute anum)))))
 (fields (attr () (j1)))))

Correspondance des champs calculés

La classe *Computed-field* correspond à une catégorie de champs atomiques un peu par-
ticulière puisqu'il s'agit de celle des champs calculés. Ces champs contiennent également
des valeurs atomiques, mais qui sont les résultats de calculs sur la base. La correspondance
d'un champ de ce type est cette fois l'expression d'un calcul.

Nous avons vu que les termes de ce calcul sont exprimés par l'utilisateur sous forme
d'une lambda-expression, englobée dans un environnement où on précise quels attributs
sont associés aux variables (cf. §3.2.5.3). Cette lambda-expression "englobée" est conser-
vée dans le champ *a-lambda* (figure 11).

<caption>Listing 11. Les classes des champs calculés</caption>

(driver-defclass Computed-field Driver-field
 (a-lambda atom ()))

(driver-defclass Mono-tuple-expr Computed-field)

(driver-defclass Multi-tuple-expr Computed-field
 (groupedp atom symbol))

(driver-defclass-map Computed-field db_field
 (attrconstraint ((lambda (attr) (memq attr '(monotexpr multitexpr))))
 (db_field classfield)))))

 fields (a-lambda (db_field a ()
 (filter cf-alambda))))

(driver-defclass-map Mono-tuple-expr db_field
 (attrconstraint ((lambda (attr) (eq attr 'monotexpr)))
 (db_field classfield)))))
(driver-defclass-map Multi-tuple-expr db_field
 (attrconstraint ((lambda (attr) (eq attr 'multitexpr))
 (db_field classfield)))))
 (fields (groupedp (db_field groupedp) ())))

Bien que seul champ déclaré de la classe `Computed-field`, `a-lambda` est en fait accompagné de deux autres. Ceux-là n'ont pas d'intérêt à être persistant car leurs contenus peuvent être déduits de celui de `a-lambda`. Le premier contient l'expression du calcul compilée sous forme d'objets système. Ces objets, qui sont décrits en §4.1.4, permettent notamment de générer facilement l'expression du calcul sous une forme SQL. Le second champ contient l'ensemble des attributs logiques associés aux variables de la lambda-expression.

La classe `Computed-field` a deux sous-classes, `Mono-tuple-expr` et `Multi-tuple-expr`. Elles correspondent aux deux types de calcul possibles sur la base, calcul multi-n-uplet et calcul multi-n-uplets (cf. §3.1.3.2 et §3.2.3.7), identifiés par les mots réservés `monotexpr` et `multitexpr`.

La classe `Mono-tuple-expr` n'a pas de champ propre, persistant ou non.

La classe `Multi-tuple-expr` en possède trois, dont un seul est persistant. Le champ `groupedp` contient un booléen qui vaut "vrai" quand la clause SQL `group by` est nécessaire dans la requête. Sa valeur est "vrai" par défaut.

Les deux autres permettent de stocker l'objet requête correspondant au champ ainsi qu'un dictionnaire donnant accès aux valeurs de ce champ pour chaque objet. La clé d'une valeur est l'identificateur relationnel de l'objet "propriétaire". Ce dictionnaire est construit après interrogation de la base (cf. §4.1.4.3). Pendant le processus de construction des objets en mémoire, il est utilisé pour restituer à chaque objet sa valeur pour ce champ.

Un filtre a été défini entre le champ `a-lambda` et son attribut correspondant. Lors de l'écriture dans la base, il assure la conversion de l'expression Lisp en chaîne de caractères.

Par ailleurs, un appel à la méthode `driver-object-loaded` termine le chargement d'un objet (cf. §4.1.2.2). Les deux méthodes définies pour `Mono-tuple-expr` et `Multi-tuple-expr` assurent la reconstruction des champs non persistants.

## Correspondance du champ objet

La classe `Object-field` est la classe des champs de type "objet". Elle n'a qu'un champ persistant `class` (listing 12) qui contient la classe de l'objet contenu. Comme nous l'avons vu, la chaîne de jointures correspondance de ce type de champ est stockée dans un champ propre à la classe `Driver-field`.
<caption>Listing 12. Le champ objet et sa correspondance</caption>

```
(driver-defclass Object-field Driver-field
  (class object Driver-class))

(driver-defclass-map Object-field db_field
  (attrconstraint ((lambda (attr) (eq attr 'object'))
                    (db_field classfield)))
  (letjoins ((j1 (db_field db_class
                    ((lambda (a1 a2) (eq a1 a2))
                      (db_field objclass) (db_class cnum))))
            (fields (class () (j1)))))
```

Object-field possède aussi quatre autres champs, non persistants car non décrits dans les déclarations de la classe.

L'un d'entre eux contient toutes les jointures élémentaires de la chaîne correspondante, c'est-à-dire toutes sauf la dernière qui est la jointure objet. Un autre contient l'ensemble des attributs composant la clé de la relation associée à la classe de l'objet contenu. Un autre encore contient l'ensemble des attributs comparés par égalité aux attributs prédé-dents dans la jointure objet. Ce sont les valeurs de ces derniers qui sont demandées au SGBD dans la requête de la classe et qui permettent de déterminer la référence des ob-jets contenus. Enfin, le dernier contient des informations relatives à la composition de la jointure objet.

Une méthode driver-object-loaded a également été définie pour la classe Object-field. Elle est appelée dès qu'un objet champ de type "objet" a été chargé. Elle assure ici aussi la reconstruction des champs non persistants à partir des informations contenues dans ceux qui le sont.

## Correspondance des champs ensemble

La classe Set-field est la classe des champs de type "ensemble". Elle ne possède aucun champ persistant propre mais deux champs non persistants qui jouent les mêmes rôles que les champs non persistants de la classe Multi-tuple-expr. Nous avons vu que, à l'instar de ces champs calculés "multi-n-uplets", les champs "ensemble" nécessitent une requête individuelle pour déterminer leurs valeurs. L'un de ces deux champs non pers-sistants contient la requête de lecture du champ dès qu'elle est construite, l'autre, un dictionnaire qui, après interrogation de la base, contient les valeurs pour ce champ des objets en cours de chargement. Ces valeurs sont accessibles par les identificateurs rela-tionnels des objets.
<caption>Listing 13. Les champs "ensemble"</caption>

(driver-defclass Set-field Driver-field)

(driver-defclass Atom-set Set-field
 (attr object Driver-attribute))

(driver-defclass Ordered-atom-set Atom-set
 (orders ordobjset Driver-order))

(driver-defclass Object-set Set-field
 (setobjclass object Driver-class))

(driver-defclass Ordered-object-set Object-set
 (orders ordobjset Driver-order))

Atom-set est la classe des champs de type "ensemble d'atomes". Elle comprend un champ attr qui référence l'attribut logique correspondance du champ.

Ordered-atom-set est une sous-classe d'Atom-set, spécialisée sur les ensembles or-
donnés d'atomes. L'ordre des éléments est précisé par des objets Driver-order contenu
dans le champ orders. Le premier précise l'ordre primaire, le second l'ordre secondaire,
et ainsi de suite.

Object-set est la classe des champs de type "ensemble d'objets" de classe connue.
Elle possède un champ persistant setobjclass qui référence la classe de l'objet contenu.
Un autre, non persistant, existe. Il contient l'ensemble des attributs composant la clé de
la relation associée à la classe des objets contenus.

Ordered-object-set est le pendant de la classe Ordered-atom-set pour Object-set.
Elle possède le même champ orders pour gérer l'ordre des objets dans chaque ensemble.

<caption>Listing 14. Correspondances des champs "ensemble"</caption>

(driver-defclass-map Set-field db_field
 (attrconstraint ((lambda (attr)
 (memq attr '(atomset ordatomset objectset
             ordobjset object2set ordobj2set))
 (db_field classfield)))))

(driver-defclass-map Atom-set db_field
 (attrconstraint ((lambda (attr) (memq attr '(atomset ordatomset
             object2set ordobj2set)))
 (db_field classfield)))))
```
 letjoins ((j1 (db_field db_attribute
             ((lambda (a1 a2) (eq a1 a2))
              (db_field attr) (db_attribute anum))))
          (fields (attr () (j1)))))

 (driver-defclass-map Ordered-atom-set db_field
     (attrconstraint ((lambda (attr) (memq attr ' (ordatomset ordobj2set)))
                        (db_field classfield))))
 letjoins ((j1 (db_field db_order
             ((lambda (a1 a2) (eq a1 a2))
              (db_field fnum) (db_order field)))))
          (fields (orders () (j1) (orders ((db_order rank) asc)))))

 (driver-defclass-map Object-set db_field
     (attrconstraint ((lambda (attr) (memq attr ' (objectset ordobjset)))
                        (db_field classfield))))
 letjoins ((j1 (db_field db_class
             ((lambda (a1 a2) (eq a1 a2))
              (db_field objclass) (db_class cnum)))))
          (fields (setobjclass () (j1)))))

 (driver-defclass-map Ordered-object-set db_field
     (attrconstraint ((lambda (attr) (eq attr 'ordobjset))
                        (db_field classfield))))
 letjoins ((j1 (db_field db_order
             ((lambda (a1 a2) (eq a1 a2))
              (db_field fnum) (db_order field)))))
          (fields (orders () (j1) (orders ((db_order rank) asc)))))
```

Les classes `Object2-set` et `Ordered-object2-set` sont les sous-classes respectives de `Atom-set` et `Ordered-atom-set`. Ceci explique les contraintes imposées à l'attribut `classfield` pour ces deux classes.

## Les champs objet de classe inconnue

Un champ de type "objet de classe inconnue" est associé, nous l'avons vu, à un attribut dans lequel est stockée la référence relationnelle de l'objet sous forme d'une chaîne de caractères. De ce fait, toutes les classes de cette famille sont les sous-classes des classes "atomiques" correspondantes, à savoir `Atom-field` pour `Object2-field`, `Atom-set` pour `Object2-set` et `Ordered-atom-set` pour `Ordered-object2-set`.

Listing 15. Les champs objet2 et leurs correspondances

```
 driver-defclass Object2-field Atom-field
```
(driver-defclass Object2-set Atom-set)

(driver-defclass Ordered-object2-set Ordered-atom-set)

(driver-defclass-map Object2-field db_field
 (attrconstraint ((lambda (attr) (eq attr 'object2'))
 (db_field classfield)))))

(driver-defclass-map Object2-set db_field
 (attrconstraint ((lambda (attr) (eq attr 'object2set'))
 (db_field classfield)))))

(driver-defclass-map Ordered-object2-set db_field
 (attrconstraint ((lambda (attr) (eq attr 'ordobj2set'))
 (db_field classfield)))))

De ce fait, les correspondances à ces classes ne comprennent que les restrictions des contraintes imposées aux classes mères.

3.3.2.5 La classe Ordre et sa correspondance

Un objet de la classe Driver-order permet de décrire dans quel ordre des n-uplets solu-
tions à une requête doivent être retournés à DRIVER pour traitement. Cet ordre conditionne
celui dans lequel les valeurs issues des n-uplets vont être placées dans le champ.

L'ordre est déterminé par la valeur d'un attribut stocké dans le champ attr. Le tri
peut se faire par ordre croissant ou décroissant. Selon le cas, le champ ordtype vaut asc
(ascendant) ou desc (descendant).

<caption>Listing 16. La classe Ordre et sa correspondance</caption>

(driver-defclass Driver-order ()
 (attr object Driver-attribute)
 (ordtype atom symbol))

(driver-deftable db_order
 (ordnum integer 2 keypart)
 (field integer 2())
 (attr integer 2())
 (ordtype string 4())
 (rank integer 2()))

(driver-defclass-map Driver-order db_order
 (letjoins ((j1 (db_order db_attribute
((lambda (a1 a2) (eq a1 a2))
(db_order attr (db_attribute anum))))
 fields (attr () (j1))
 (ordtype (db_order ordtype) ())))

Dans notre implantation, un objet `Driver-order` est attaché à un champ. C'est pourquoi la relation `db_order` contient le numéro du champ correspondant.

Ce champ, qui est bien sûr de type "ensemble ordonné", peut être en relation avec plusieurs objets `Driver-order`. L'un définit l'ordre primaire, un autre l'ordre secondaire, et ainsi de suite. Le rang de chacun est stocké dans l'attribut `rank` de la relation `db_order`.

De façon réflexive, cet attribut est utilisé dans la correspondance des champs `orders` des classes `Ordered-atom-set` et `Ordered-object-set`. Il détermine lors du chargement l'ordre des objets `Db_order` dans ces champs.

### 3.3.2.6 Jointures, contraintes et leurs correspondances

Les jointures et les contraintes sont, en `DRIVER`, des objets instances de classes ayant même racine `Driver-join0` (cf. figure 3.26). Leurs natures sont similaires. Leur rôle commun est de préciser quelles règles doivent respecter les n-uplets filtrés pour être des objets relationnels de telle ou telle classe.

<figure><img src="image_17.png" /><figcaption>Figure 3.26 : Hiérarchie des classes de contraintes et jointures</figcaption></figure>

Dans chaque cas, leur expression est demandée au concepteur du schéma sous forme d'une lambda-expression, nue pour les contraintes, englobée pour les jointures dans un environnement où on précise quels attributs logiques sont associés aux variables (cf. §3.1.3.2 et §3.2.5.2). Cette lambda-expression "englobée" (qui est reconstituée en ce qui concerne les contraintes) est conservée dans le champ `a-lambda` (listing 17).

<caption>Listing 17. Jointures et contraintes</caption>

(driver-defclass Driver-join0 ()
(a-lambda atom ()))
(driver-defclass Driver-restriction Driver-join0
(class object Driver-class)
(field object Driver-field)
(attr object Driver-attribute)
(joins objectset Driver-join))

(driver-defclass Driver-join Driver-join0
(table1 object Driver-table)
(table2 object Driver-table))

Driver-join0 possède un autre champ, non persistant cette fois, qui contient la représentation objet de la “a-lambda”. Cette représentation est construite par le compilateur CLERIC<fnref n="7" /> de DRIVER (cf. §4.4.2). Elle est formée d’objets système dont les classes sont décrites en §4.1.4. Ces objets répondent à un certain nombre de messages, notamment à generate-sql qui retourne une expression SQL directement utilisable dans une requête.

Une contrainte aide à préciser une classe : cette classe est référencée dans class. Elle est posée sur un champ ou un attribut; selon le cas, les champs field et attr permettent d'accéder à l'objet correspondant.

Enfin, l'éventuel attribut sur lequel est posé la contrainte peut se trouver dans une table secondaire associée à la classe. Dans ce cas, la chaîne de jointures qui permet de l'atteindre est contenue dans joins.

Une jointure permet d'atteindre une table logique cible à partir d'une table logique de départ. La première est accessible par le champ table1, la seconde par le champ table2.

<caption>Listing 18. Correspondances des jointures et contraintes</caption>

(driver-deftable db_join0
(jnum integer 2 keypart)
(mapping integer 2())
(jtype string 12())
(aoss string 200())
(class integer 2())
(field integer 2())
(attr integer 2())
(table1 integer 2())
(table2 integer 2()))

 driver-deftable db_restrijoins
 (restri integer 2 keypart)
 (join integer 2 keypart))

<footnote n="7">⁷Compilateur de Lambda Expression en Représentation Intermédiaire de Contrainte.</footnote>
(driver-defclass-map Driver-join0 db_join0
 (fields (a-lambda (db_join0 aosslambda) ()
 (filter j0-alambda))))

(driver-defclass-map Driver-restriction db_join0
 (attrconstraint ((lambda (attr) (eq attr 'restriction))
 (db_join0 jtype)))))
 (letjoins ((j1 (db_join0 db_class
 ((lambda (a1 a2) (eq a1 a2))
 (db_join0 class) (db_class cnum)))))
 (j2 (db_join0 db_field
 ((lambda (a1 a2) (eq a1 a2))
 (db_join0 field) (db_field fnum)))))
 (j3 (db_join0 db_attribute
 ((lambda (a1 a2) (eq a1 a2))
 (db_join0 attr) (db_attribute anum)))))
 (j4 (db_join0 (join db_join0)
 ((lambda (a1 a2 a3 a4)
 (and (eq a1 a2) (eq a3 a4)))
 (db_join0 jnum) (db_restrijoins restri)
 (db_restrijoins join) (join jnum)))))
 (fields (class () (j1))
 (field () (j2))
 (attr () (j3))
 (joins () (j4)))))

(driver-defclass-map Driver-join db_join0
 (attrconstraint ((lambda (attr) (eq attr 'join))
 (db_join0 jtype)))))
 (letjoins ((j1 (db_join0 (t1 db_table)
 ((lambda (a1 a2) (eq a1 a2))
 (db_join0 table1) (t1 tnum)))))
 (j2 (db_join0 (t2 db_table)
 ((lambda (a1 a2) (eq a1 a2))
 (db_join0 table2) (t2 tnum)))))
 (fields (table1 () (j1))
 (table2 () (j2)))))

L'appartenance d'un objet relationnel à telle ou telle classe est fonction de la valeur
de l'attribut db_join0.jtype qui vaut restriction ou join selon les cas.

Un filtre a été défini pour le champ a-lambda de Driver-join0. Il permet de transformer l'expression Lisp en chaîne de caractères pour l'écriture dans la base, et vice-versa.

Des méthodes driver-object-loaded ont été définies pour chacune des classes
Driver-restriction et Driver-join. Elles assurent la reconstruction de la représen-
tation objet de la “a-lambda” à partir du contenu des différents champs persistants.
# Chapitre 4

## COMMUNICATIONS ET TRANSFERTS DE DONNÉES
# 4.1 Chargement des objets relationnels

## 4.1.1 Généralités

### 4.1.1.1 Déclenchement d'un chargement

Dans la couche objet virtuelle, la construction d'un objet jumeau peut survenir de deux façons différentes :

* Elle peut d'une part être explicitement demandée. Pour des raisons d'efficacité, il est en effet parfois préférable de charger au préalable dans l'environnement des objets dont on sait qu'on va ensuite en avoir besoin. Quand les accès aux objets relationnels sont nombreux, il peut être lourd de les charger au coup par coup, au hasard d'accès à des objets encore représentés par des défauts d'objet.

* Elle se produit également par effet de bord, à la suite d'un envoi de message ou d'une tentative d'accès sur un objet relationnel encore représenté par un défaut d'objet. L'objet jumeau est construit et substitué au défaut avant que ne soit réellement effectué l'envoi de message ou l'écriture - lecture du champ.

Dans le premier cas, l'utilisateur fait directement appel à la primitive **driver-load-class-objects**. Cette fonction permet de charger dans l'environnement un ensemble d'objets relationnels de même classe principale. Sont requises en arguments la classe concernée et les valeurs des clés des objets relationnels à charger.

Dans le second, le système invoque la primitive **driver-load-object-default** dont l'algorithme est présenté figure 4.2. Cette fonction fait elle-même appel à **driver-load-class-objects**.

### 4.1.1.2 Chargement à partir d'un défaut d'objet

Un défaut d'objet peut au besoin procéder au chargement de l'objet relationnel qu'il présente car il contient toutes les informations nécessaires pour l'identifier et le charger.

La classe **driver-object-default**, classe des défauts d'objets, comprend un champ **key**, défini pour recueillir l'identificateur relationnel de l'objet représenté (listing 19). Le champ **mapping** contient le schéma dans lequel sont décrites ses correspondances.

Deux autres champs contiennent des informations qui peuvent être déduites des deux premières. Elles sont malgré tout déterminées et stockées une fois pour toute à la création du défaut car elles sont coûteuses à calculer mais nécessaires pour toutes opérations de
lecture de données dans la base qui implique le défaut.

<table><caption>Listing 19. Classe des défauts d'objet</caption><tbody><tr><td>(driver-defclass</td><td>driver-object-default ()</td></tr><tr><td>(mapping</td><td>object Driver-mapping)</td></tr><tr><td>(main-class</td><td>object Driver-class)</td></tr><tr><td>(key</td><td>atom symbol)</td></tr><tr><td>(expanded-key</td><td>atom ()))</td></tr></tbody></table>

La première, contenue dans le champ `expanded-key`, est la valeur de la clé du n-uplet de l'objet relationnel représenté. Elle est déduite de l'identificateur de l'objet, composé, rappelons-le, du nom de la relation principale et d'une valeur de clé dans cette relation.

La seconde est la classe principale de l'objet relationnel représenté. Elle est déduite du nom de la relation principale et du schéma dans lequel sont précisées les correspondances de chaque relations. Une fois déterminée, elle est conservée dans le champ `main-class`.

Un défaut d'objet est présenté figure 4.1. Il représente l'objet relationnel de classe `Vendeur` et de nom `allen`.

<figure><img src="image_18.png" /><figcaption>Figure 4.1 : Un défaut d'objet dans l'environnement de SMECI</figcaption></figure>

On remarquera (listing 19) que `driver-object-default` est décrite dans le modèle objet de `DRIVER`. Ce n'est bien sûr pas pour lui apporter la persistance, ce qui n'a aucun sens. L'intérêt réside dans le fait que cette déclaration se traduit par la création de la classe correspondante dans le modèle client.

`driver-object-default` ne peut être qu'une classe du modèle client et en aucun cas une classe système. Lors de la construction d'un objet jumeau précédemment représenté dans l'environnement par un défaut d'objet, il est nécessaire de faire muter l'objet correspon-
dant de la classe driver-object-default vers la classe de l'objet relationnel. Pour que cette
opération soit possible, les deux classes doivent être définies dans le même modèle objet,
donc dans le modèle client.

4.1.1.3 Gestion des objets manquants

<figure><img src="image_19.png" /><figcaption>Figure 4.2 : Algorithme de chargement d'un défaut d'objet</figcaption></figure>

La figure 4.2 présente l’algorithme de chargement d’un défaut d’objet. L’opération consiste principalement à charger l’objet relationnel au moyen de la fonction driver-load-class-objects après avoir précisé le schéma de travail.

Il peut arriver que le défaut d'objet ait été défini pour un identificateur relationnel ne correspondant à aucun n-uplet. Ceci peut se produire par exemple quand l'intégrité référentielle n'a pas été rétablie après une suppression de n-uplet effectuée dans la base par une transaction antérieure ou concurrente. Le défaut représente alors un objet relationnel dit *manquant*.

La tentative de chargement d'un tel objet échoue naturellement car le SGBD ne trouve
aucune solution à la requête définie pour lire dans la base le n-uplet correspondant. Faute
d'informations, la fonction driver-load-class-objects laisse le représentant de l'objet man-
quant sous forme de défaut d'objet.

Un défaut d'objet représente nécessairement un objet relationnel manquant quand une
tentative de chargement ne produit aucun effet sur lui et qu'il reste un défaut d'objet.
La primitive driver-missing-object-processing est alors appelée. Cette fonction provoque par
défaut une erreur "Missing relational object", mais elle peut être redéfinie par l'utilisateur
pour effectuer un traitement propre à son application.
## 4.1.2 L'algorithme de chargement

### 4.1.2.1 Coût en requêtes

Comme tous les algorithmes de `DRIVER` amenés à dialoguer avec le `SGBD` sous-jacent, l'algorithme de chargement des objets `load-class-objects` (figure 4.3) est conçu pour minimiser le nombre de requêtes adressées à la base. En effet, les coûts en temps de l'envoi d'une requête, de son traitement par le `SGBD` et de la récupération du résultat rendu sont tels que ce choix était nécessaire pour obtenir les meilleures performances. On peut ajouter que la complexité d'une requête n'influe que très peu sur le temps de traitement dont la plus grande partie est due aux transferts de données entre l'environnement et la base.

Dans cette perspective, nous nous sommes attachés à minimiser le nombre de requêtes nécessaires pour charger un groupe d'objets d'une classe donnée. `DRIVER` permet le chargement de la plupart des champs propres à une classe au moyen d'une requête principale, la réponse à cette requête comprenant un n-uplet par objet relationnel. Seuls les champs de type "ensemble" et "calcul multi-n-uplets" requièrent d'être chargés à part, au moyen d'une requête supplémentaire pour chacun d'entre eux. En effet, les réponses à ces requêtes comportent plusieurs n-uplets pour une seule valeur de champ (cf. §4.1.4.3).

Si une requête permet de charger en une fois tous les champs propres à une classe (abstraction faite des champs de type "ensemble" et "calcul multi-n-uplets"), elle ne permet cependant pas de charger en même temps les champs hérités des classes ancêtres. Une requête est nécessaire par classe élément de la hiérarchie. Ainsi, par exemple, si un n-uplet de la relation `emp` (figure 3.1) est un objet relationnel de la classe `Chef-de-service` ( figure 3.3), trois requêtes sont nécessaires pour le charger : une pour charger les champs de la classe `Employé`, une pour charger ceux de la classe `Cadre`, et enfin, une dernière pour charger ceux de la classe `Chef-de-service`.

Par ailleurs, on ignore de quelle classe sera précisément un objet avant de le charger; on n'est sûr que de sa classe principale. Pour cette raison, le système est tenu d'envoyer au `SGBD` une requête pour chacune des classes de la hiérarchie. Quand l'objet est au moins instance de la classe pour laquelle une requête a été envoyée, la réponse comprend un n-uplet contenant les valeurs de ses champs propres à cette classe. Dans le cas contraire, elle ne comprend aucun n-uplet pour l'objet en question.

Certains se demanderont pourquoi tous les champs de l'objet ne peuvent être chargés en une seule fois. Cela n'est en fait possible que si on connaît la classe précise des objets concernés avant l'envoi de la requête de chargement.

Un autre algorithme de chargement aurait en effet pu être d'interroger d'abord la base sur la classe des objets à charger, puis d'envoyer une requête pour chaque classe dont on a au moins un objet à charger. Un avantage de cet algorithme aurait été d'éviter que `DRIVER` ait besoin d'une primitive de mutation de classe (cf. §3.1.2.3). En effet, on connaît ici la classe des objets au moment de les créer.
Soient $N$ le nombre de classes de la hiérarchie, $M$ le nombre de champs de type "ensemble" et "calcul multi-n-uplets" qu'elle comprend,
le coût $C$ de cet algorithme (en nombre de requêtes) est :

$$C = (N + \alpha + \beta)$$
avec $1 \le \alpha \le N$ et $0 \le \beta \le M$.

Une requête est en effet nécessaire par classe pour déterminer quels objets en sont instances. De une à $N$ autres sont également nécessaires pour charger les objets, selon qu'au mieux, ils sont tous précisément de la même classe, et, qu'au pire, l'ensemble comprend au moins un objet de chaque classe. $\beta$ représente pour sa part le nombre de champs de type "ensemble" et "calcul multi-n-uplets" que comprennent les classes dont au moins un objet est à charger.

Pour la même opération, le coût $C'$ de l'algorithme utilisé par `DRIVER` est :

$$C' = (N + \beta)$$

Il nécessite une requête par classe, plus une requête par champ de type "ensemble" et "calcul multi-n-uplets" à charger. Comme ne sont traités parmi ces champs que ceux qui appartiennent aux classes dont au moins un objet est à charger, $\beta$ a la même valeur dans les deux coûts. On constate que l'algorithme utilisé par `DRIVER` est plus économique. On verra d'autre part que, du fait d'une optimisation, son coût est plus exactement majoré par $(N + \beta)$ (cf. §4.1.2.3).

Afin de préserver sa clarté, l'algorithme de chargement présenté dans la figure 4.3 ne comprend pas l'optimisation dont on vient de faire état. La version complète sera explicitée dans un deuxième temps (figure 4.4).

### 4.1.2.2 Détails de l'algorithme

La fonction `driver-load-class-objects`, accessible à l'utilisateur final, permet de charger dans l'environnement en une opération un nombre quelconque d'objets relationnels de même classe principale. Les objets choisis doivent être désignés au moyen de la valeur de clé de leurs n-uplets respectifs. Si la liste de clés est remplacée par le symbol `t`, tous les objets de la classe sont chargés.

L'ensemble des valeurs de clé indiquées est compilé sous forme d'une expression restrictive permettant, lors des requêtes au SGBD, de ne filtrer que les n-uplets des objets concernés. Cette expression est en réalité structurée sous forme d'objets système dont les classes sont décrites en §4.1.4. Ces objets répondent à un certain nombre d'envois de message comme `generate-sql` qui retourne une expression SQL directement utilisable dans une requête. L'objet qui représente cette expression restrictive est désigné sous le
<figure><img src="image_20.png" /><figcaption>Figure 4.3 : Algorithme de chargement d'objets de même classe</figcaption></figure>
nom d'opérateur de filtrage d'objets (*Object_filter_operator* dans les descriptions d'algorithme).

Une fois l'opérateur de filtrage construit, *driver-load-class-objects* fait appel à *load-class-objects* dont l'algorithme est présenté dans la figure 4.3.

*load-class-objects* utilise la primitive *all-subclasses* pour déterminer l'ensemble des classes héritant de la classe principale. Ceci permet d'effectuer ensuite, pour chacune d'entre elles, le chargement de leurs instances sélectionnées par l'opérateur de filtrage.

L'ordre dans lequel se trouvent les classes dans la variable *Classes* est important. Une classe fille doit toujours être placée après sa mère. En effet, quand, suite à l'interrogation de la base, un objet est reconnu comme étant au moins instance de la classe en cours de traitement, il est muté vers cette nouvelle classe. De cette façon, au fil du processus s'affine la classe de chaque objet.

Reprenons l'exemple du n-uplet de la relation *emp*, support d'un objet relationnel de la classe *Chef-de-service*. Au début du chargement, la variable *Classes* vaut `{Employé, Vendeur, Cadre, Président, Chef-de-service}`. Le processus commence donc par le chargement des instances de la classe *Employé*. Chef de service, notre objet relationnel est a fortiori reconnu comme étant un employé. Si l'objet était auparavant représenté dans l'environnement par un défaut d'objet, d'instance de *driver-object-default* il devient instance d'Employé. S'il était déjà représenté par un objet jumeau, de la classe de cet objet jumeau (*Chef-de-service* ou autre) il redevient employé.

Après chargement des champs propres de la classe *Employé*, le processus continue par le chargement des instances de la classe *Vendeur*, car cette classe est la suivante dans la variable *Classes*. Notre objet n'étant pas un vendeur, cette opération n'a aucune réperution sur lui. Suit le traitement de la classe *Cadre*. Il est cette fois concerné. Reconnu comme étant instance de cette classe, d'employé il devient cadre. Les champs propres à la classe *Cadre* sont affectés. La classe *Président* est la suivante. Notre objet n'étant pas reconnu comme président, cette opération n'a pas non plus d'incidence sur lui. Le processus se termine par le chargement des objets *Chef-de-service*. Étant reconnu comme tel, notre cadre est promu chef de service et ses champs propres à cette classe sont construits.

On comprend pourquoi une classe fille ne peut jamais se trouver avant sa mère dans la liste contenue dans la variable *Classes*. Si cette règle n'était pas respectée, l'algorithme de chargement serait mis en défaut. On ne pourrait jamais avoir dans l'environnement des objets jumeaux instances de classes placées avant leurs mères dans la variable *Classes*.

Dans l'exemple précédent, admettons que les classes *Cadre* et *Chef-de-service* soient inversées dans *Classes*. Cette variable aurait pour valeur `{Employé, Vendeur, Chef-de-service, Président, Cadre}`. Après traitement de la classe *Chef-de-service*, notre précédent objet serait bien un chef de service, mais avec les champs propres à la classe *Cadre* non affectés. Lors du traitement de la classe *Cadre*, reconnu comme étant l'une de ses instances, il serait muté de *Chef-de-service* à *Cadre*. Outre le fait que sa classe
ne serait pas correcte, on perdrait en plus les valeurs de ses champs propres à la classe `Chef-de-service`.

La solution à ce problème pourrait consister à n'autoriser, en fait de mutation de classe, que la spécialisation des objets. Ce serait oublier que, si la base de données est partagée et si des modifications faites par d'autres utilisateurs se traduisent sur nos objets relationnels par des changements de classes, ceux-ci peuvent être aussi bien des généralisations que des spécialisations. Lors d'un second chargement d'objet relationnel dont la classe a évolué entre-temps, `DRIVER` doit être capable de ré-ajuster la classe de objet jumeau correspondant, que ce soit dans un sens ou dans l'autre. Généraliser un objet doit donc rester possible.

L'algorithme `load-class-part-objects` permet de charger les champs propres à une classe d'un groupe d'objets sélectionnés par un opérateur de filtrage d'objets. Il fait appel à la fonction `class-select` qui lui rend un objet requête de la classe `Driver-select` (requête de consultation de données, cf. §4.1.4). Les objets requêtes répondent à un certain nombre de messages comme `generate-sql` qui, rappelons-le, retourne la requête SQL qui leur correspond.

Pour chaque classe, la requête est bien sûr spécifique. On peut malgré tout la traduire de façon générique par :

> Parmi les n-uplets de la table principale,
> lesquels sont des objets relationnels au moins instances de cette classe ?
> Renvoyer la valeur des champs propres à la classe pour ces objets.

Complétée de l'opérateur de filtrage, la requête est restreinte aux seuls objets choisis. La fonction `ask-database` adresse au SGBD cette requête complétée de l'opérateur de filtrage et retourne l'ensemble des n-uplets correspondant aux objets solution.

L'appel à la fonction `build-class-part-object` permet ensuite de construire la partie propre à la classe de chacun de ces objets. Le représentant dans l'environnement de l'objet relationnel (jumeau ou défaut) est tout d'abord retrouvé à l'aide de sa référence contenue dans le n-uplet. Si la fonction `get-object` n'en trouve pas, elle crée alors un défaut d'objet et le retourne. L'objet recueilli est ensuite muté vers la classe considérée puis sont chargés ses champs locaux. Quand un champ a été déclaré "avec correspondance", sa valeur est extraite du n-uplet et calculée par la fonction `field-value-from-tuple`. Elle est ensuite affectée dans l'objet par la fonction `set-field-value`.

Une fois tous les champs chargés, le message `driver-object-loaded` est envoyé à l'objet. La méthode `driver-object-loaded`, qui par défaut ne fait rien, peut être redéfinie pour chaque classe. En fonction de sa classe, l'objet peut ainsi se voir appliquer des traitements appropriés, comme la reconstruction de champs non persistants à partir des informations contenues dans ceux qui viennent d'être chargés. À titre d'exemple, la recompilation de nombreux objets du schéma de correspondances est effectuée au travers de cet envoi de
message lors du chargement d'un schéma contenu dans la base de données relationnelles (cf. §3.3.2.4 et §3.3.2.6).

### 4.1.2.3 Optimisation de l'algorithme de chargement

Si on utilise l'algorithme présenté dans la figure 4.3, le coût de chargement d'un ensemble d'objets de même classe principale est de ($N + \beta$) requêtes, comme nous l'avons vu.

En réalité, celui qui est effectivement utilisé par DRIVER (figure 4.4) a un coût $C'$ qui est plus précisément majoré par ce nombre :

$$C' \leq N + \beta$$

Il bénéficie en effet d'une optimisation qui réduit le nombre de requêtes à adresser au SGBD au strict nécessaire.

Au fur et à mesure qu'avance le processus de chargement se précisent les classes des objets en cours de construction. Quand un objet est reconnu comme étant (au moins) instance d'une certaine classe de la hiérarchie, on peut aussitôt en conclure qu'il n'est pas instance d'une des sœurs de cette classe. Du fait de ce principe, on est parfois en mesure de détecter des classes non encore traitées et dont les objets en cours de chargement ne peuvent être les instances. Ces classes et toutes leurs descendances sont alors enlevées de la file d'attente.

L'élagage de l'arbre des classes est très économique car il évite ainsi la construction et l'envoi au SGBD de requêtes sans intérêt qui sont malgré tout aussi coûteuses en temps que les autres.

L'algorithme optimisé est présenté figure 4.4. On peut remarquer que l'ensemble des classes à traiter est géré sous forme d'une file d'attente. L'intérêt de ceci est double. D'une part, il est possible d'accéder par cette file aux classes en attente d'être traitées et de connaître ainsi l'état d'avancement du chargement. Enfin, le contenu de la file peut facilement être modifié, par exemple, pour retirer une classe dont le traitement est inutile.

Dans notre syntaxe, une file se "lit" de la gauche vers la droite, le prochain élément à en sortir étant le plus à gauche.

Illustrons le nouveau processus de chargement par un exemple. Soit la hiérarchie de classes décrite dans la figure 4.5, considérons le chargement de deux objets relationnels désignés par o5 et o8, instances respectives des classes C5 et C8. Au début du processus, la variable *Class_queue* vaut, par exemple<fnref n="1" />, {Main_class, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13}.

Consécutivement au traitement de *Main_class*, aucune réduction de la file d'attente n'est possible car on n'a pas encore suffisamment d'information sur la classe des deux

<footnote n="1">¹L'ordre n'est pas strict. La seule règle est qu'une classe fille soit nécessairement derrière sa mère dans la file d'attente.</footnote>
**algorithme** load-class-objects(*Main_class*, *Object_filter_operator*)
**soit** *Class_queue* ← all-subclasses(*Main_class*)
**et** *Objects* ← load-class-part-objects(*Main_class*, *Object_filter_operator*);
**tant que** *Class_queue* ≠ {}
    **soit** *C* ← pop-element(*Class_queue*);
    **faire** load-class-part-objects(*C*, *Object_filter_operator*)
    **et** reduce-class-queue(*Class_queue*, *Objects*);
**fin**

**algorithme** load-class-part-objects(*Class*, *Object_filter_operator*)
**soit** *Select* ← class-select(*Class*)
**et** *Tuples* ← ask-database(*Select*, *Object_filter_operator*)
**et** *Objects* ← {};
**pour tout** *T* ∈ *Tuples*
    **soit** *Object* ← build-class-part-object(*Class*, *Select*, *T*);
    **faire** *Objects* ← *Objects* ∪ {*Object*};
**retourner** *Objects*;
**fin**

**algorithme** build-class-part-object(*Class*, *Select*, *Tuple*)
**soit** *Key* ← object-key-from-tuple(*Select*, *Tuple*)
**et** *Object* ← get-object(*Key*);
**faire** change-object-class(*Class*, *Object*);
**pour tout** *F* ∈ fields.*Class*
    **faire si** mappedp.*F* = ∅
        **alors** soit *Fvalue* ← field-value-from-tuple(*F*, *Select*, *Tuple*);
        **faire** set-field-value(*F*, *Object*, *Fvalue*);
    **faire** driver-object-loaded(*Class*).*Object*;
    **retourner** *Object*;
**fin**

<caption>Figure 4.4 : Algorithme optimisé de chargement d'objets de même classe</caption>
<figure><img src="image_21.png" /><figcaption>Figure 4.5 : Une hiérarchie de classes</figcaption></figure>

objets. Il en est de même suite au traitement de C1, car si o5 a été reconnu comme une instance de C1, cela n'a pas été le cas de o8. On ignore donc encore à quelle sous-hiérarchie appartient cet objet. La fonction reduce-class-queue laisse Class_queue à {C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13}.

C2 reconnaît ensuite o8 comme étant l'une de ses instances. Suite au traitement de cette classe, l'ensemble des classes encore à interroger peut être réduit, car on sait à quelles sous-hiérarchies appartiennent les objets à charger, lesquelles sont pertinentes à explorer. Suite au traitement de C2, la fonction reduce-class-queue réduit Class_queue de {C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13} à {C5, C6, C7, C8, C9}.

De la même façon, suite au traitement de C5, reduce-class-queue réduit la file de {C6, C7, C8, C9} à {C7, C8, C9}. On évite ainsi de traiter C6. Le traitement de C7 qui vient après ne permet pas de réduire la file d'attente. Enfin, celui de C8, reduce-class-queue réduit Class_queue de {C9} à {}.

Le chargement est terminé.

On constate, par cet exemple, que la réduction de la file d'attente peut être extrêmement importante et faire gagner un temps considérable. La hiérarchie est formée ici de 14 classes. Si on ignore les champs de type "ensemble" et "calcul multi-n-uplets", 6 requêtes seulement ont été nécessaires pour construire nos deux objets dans leurs classes respectives.

## Algorithme de réduction de la file d'attente des classes

L'algorithme de la fonction reduce-class-queue qui réduit la file d'attente des classes est donné dans la figure 4.6.

Son principe est simple. Soit une classe C dans la file d'attente, s'il n'existe pas au moins un objet parmi ceux en cours de chargement dont la classe est ancêtre de C, alors C et toutes ses sous-classes peuvent être enlevées de la file sans préjudice pour le chargement en cours.
```pascal
algorithmreduce-class-queue(Class_queue, Objects)
soit New_class_queue ← {};
pour tout C ∈ Class_queue
    si ∃ O ∈ Objects, subclass-p(C, object-class(O)) = ↑
    alors New_class_queue ← New_class_queue ∪ {C};
faire Class_queue ← New_class_queue;
fin
```

<caption>Figure 4.6 : Algorithme de réduction de la file d'attente des classes</caption>

## 4.1.3 Chargement récursif d'objets

### 4.1.3.1 Intérêt et danger

Pour des raisons d'efficacité, il est parfois intéressant de charger dans l'environnement en même temps qu'un objet tous ceux qui lui sont liés, tous ceux qu'il référence directement ou indirectement. Même si ce choix peut être coûteux en occupation mémoire, le gain en performances qui en résulte peut le justifier.

DRIVER a recours à cette fonctionnalité pour charger dans l'environnement un schéma de correspondances contenu dans la base de données.

Nous avons vu que la persistance pouvait être attribuée aux schémas grâce au méta-schéma (cf. §3.3.1). On peut donc a priori choisir comme schéma courant un schéma persistant dont tous les objets contenus sont encore représentés sous forme de défauts d'objet. Si chacun d'entre eux était chargé au besoin, quand il est consulté, les premières utilisations du schéma se traduirait par un nombre important de requêtes adressées au SGBD, certaines pour les besoins de l'application, la plupart pour la construction en mémoire du schéma.

On comprendra aisément qu'il est beaucoup plus efficace de procéder à un chargement complet du schéma de correspondances avant son exploitation que de construire au coup par coup les objets qui le composent selon les besoins. La différence de coût en requêtes entre les deux méthodes en témoigne (cf. §4.1.3.3).

La plupart des applications n'ont bien entendu pas intérêt à charger récursivement leurs objets persistants. Il n'est pas rare que les réseaux d'objets relationnels contenus dans la base soient tellement étoffés que le chargement récursif d'un objet provoque la construction en mémoire d'une image de la base entière.

Cette fonctionnalité doit donc être utilisée avec circonspection si on souhaite éviter de saturer inutilement l'environnement avec des données sans grand intérêt.
### 4.1.3.2 Principe et détails de l'algorithme

L'algorithme `load-deep-objects` (figure 4.7) permet de charger récursivement un groupe d'objets donné.

Son principe général est le suivant. À chaque appel de `load-deep-objects`, on détermine l'ensemble des objets liés directement ou indirectement à l'un des objets donnés en argument. On extrait ensuite de cet ensemble les défauts d'objets qui s'y trouvent et on procède à leur chargement. Après leur construction, les objets nouvellement chargés sont à leur tour confiés à `load-deep-objects`.

De cette façon, tous les objets référencés sont chargés de proche en proche. L'appel récursif à `load-deep-objects` s'arrête quand le groupe d'objets passé en argument ne contient plus directement ou indirectement de défaut d'objet.

```
algorithme driver-load-deep-objects(Objects)
    faire driver-rollback-objects(Objects)
    et load-deep-objects(Objects);
    retourner Objects;
fin
```

```
algorithme load-deep-objects(Objects)
    soit All_objects ← driver-all-linked-objects(Objects)
    et Defaults ← get-defaults(All_objects);
    si Defaults ≠ {}
        alors soit Main_classes ← involved-main-classes(Defaults);
            pour tout MClass ∈ Main_classes
                soit MClass_objects ← main-class-objects(MClass, Defaults)
                et Object_keys ← object-default-keys(MClass_objects);
                faire driver-load-class-objects(MClass, Object_keys);
                faire load-deep-objects(Defaults);
    fin
```

<caption>Figure 4.7 : Algorithme de chargement récursif d'objets</caption>

### Détails de l'algorithme

Au début du processus, tous les objets à charger sont représentés par des défauts d'objets.

Si certains d'entre eux ont déjà été chargés, l'appel à `driver-rollback-objects` provoqué par `driver-load-class-objects` permet de les re-transformer en défauts d'objets. Il est à no-
ter que la fonction **driver-rollback-objects** n'a d'effet que sur les objets qui lui sont passés en argument. Elle ne change pas en défauts les objets liés aux premiers. Ainsi, quand le chargement récursif d'un objet a déjà été effectué, une seconde tentative ne se traduit que par le rechargement du premier niveau. Pour un réel rechargement récursif, il convient de préciser que l'on souhaite également le rechargement des objets contenus :

```
algorithme load-deep-again(Objects)
soit All_objects ← driver-all-linked-objects(Objects)
faire driver-load-deep-objects(All_objects);
fin
```

la primitive **driver-all-linked-objects** calculant l'ensemble complet des objets liés directement ou indirectement à l'ensemble passé en argument.

À l'aide de la primitive **involved-main-classes** sont déterminées les classes principales dont un objet relationnel au moins est à charger. Il va ensuite être procédé au chargement des instances de chacune d'entre elles par un appel à **driver-load-class-objects**.

Il est intéressant de remarquer que la variable *MClass_objects* ne référence pas fortement que des défauts d'objets, comme on serait tout d'abord tenté de le penser. Parfois, lors du chargement d'un champ pour lequel un filtre a été défini, se produisent par effet de bord des chargements d'objets du réseau. L'expression d'un filtre peut en effet provoquer de tels chargements. Pour éviter à ces objets d'être chargés une seconde fois, la primitive **objects-default-keys** ne retourne que les clés des objets de *Object_keys* encore représentés dans l'environnement par un défaut d'objet.

### 4.1.3.3 Coût de l'algorithme

Encore une fois, l'algorithme a été optimisé de façon à minimiser le nombre de requêtes adressées au SGBD. À chaque appel de **load-deep-objects**, une seule exécution de **driver-load-class-objects** charge tous les objets de la classe principale correspondante.

Considérons un réseau de *q* objets relationnels, chacun étant instance d'une classe parmi *N*.

Par définition, on appelle *chargeur* de ce réseau un objet ou un ensemble d'objets donnant accès, au travers des différents types de champ objet (*object*, *object2*, *objectset*, ...), à tous les éléments du réseau. Le chargement récursif de ce chargeur se traduit donc par un chargement complet du réseau dans l'environnement.

Soit un objet $o_1$, chargeur de ce réseau,
Soit $P(\{o_1\})$ le nombre d'appels à load-deep-objects nécessaire pour charger le réseau à partir d'o_1,

le coût $C''$ de chargement de ce réseau à partir de $o_1$ est :

$$C''(\{o_1\}) = \sum_{i=1}^{N} \sum_{j=1}^{P(\{o_1\})} C_{ij}$$

où $C_{ij}$ est le coût de chargement d'un groupe d'objets de classe principale $i$ lors du $j^{ème}$ appel à load-deep-objects.

Cette matrice des coûts $C$ est elle-même définie à partir de la matrice de chargement $Q$.

On a :

$$C_{ij} = \begin{cases} 0 & \text{si } Q_{ij} = 0 \\ C'_{i} & \text{si } Q_{ij} \neq 0 \end{cases}$$

où $Q_{ij}$ est le nombre d'objets de la classe $i$ chargés lors du $j^{ème}$ appel à load-deep-objects, avec

$$\sum_{i=1}^{N} \sum_{j=1}^{P(\{o_1\})} Q_{ij} = q$$

et où $C'_i$ est le coût de chargement d'un groupe d'objets de classe principale $i$. Pour simplifier les expressions, on assimilera $C'_i$ à son majorant $N + \beta$ (cf. §4.1.2.3).

$C''$ dépend du chargeur choisi. Ce coût est en général d'autant plus réduit que le chargeur choisi est en liaison directe avec un maximum d'éléments du réseau.

En comparaison, le coût $C'''$ de chargement du même réseau objet par objet est :

$$C''' = \sum_{i=1}^{q} C'_{classe(q_i)}$$

où $q_i$ est le $i^{ème}$ objet parmi les $q$, et $C'_{classe(q_i)}$ le coût de chargement d'un groupe d'objets de sa classe principale. $C'''$ est, pour sa part, indépendant du chargeur choisi.

Soit $Q_i$ le nombre d'objets de classe $i$ à charger,

$$Q_i = \sum_{j=1}^{P(\{o_1\})} Q_{ij}$$

$C'''$ devient :
$$C''' = \sum_{i=1}^{N} \sum_{j=1}^{Q_i} \underbrace{C'_{classe(q_j)}}_{C'_i} = \sum_{i=1}^{N} C'_{i} \sum_{j=1}^{Q_i} 1 = \sum_{i=1}^{N} Q_i \cdot C'_{i}$$

Comparator $C''(\{o_1\})$ et $C'''$ revient à comparer, en se restreignant à une classe $i$ donnée,

$$\sum_{j=1}^{P(\{o_1\})} C_{ij} \quad \text{et} \quad Q_i \cdot C'_i$$

La valeur de $P(\{o_1\})$ dépend de la structure du réseau<fnref n="2" />. Par conséquent, la présence de cette variable dans une expression de coût restreinte à une classe perd de sa signification et de son intérêt.

Si on fait la remarque que les appels à load-deep-objects pendant lesquels aucun groupe d'objets de la classe $i$ n'est chargé n'ont aucune influence sur la valeur de $C''(\{o_1\})$ restreinte à $i$, on constate qu'on peut remplacer dans cette expression $P$ par $\min(P(\{o_1\}), Q_i)$.

On a

$$\sum_{j=1}^{\min(P(\{o_1\}), Q_i)} C_{ij} \leq Q_i \cdot C'_i$$

Il y a égalité dans le cas d'une chaîne d'objets :

$$\begin{cases} P(\{o_1\}) = Q_i \\ \forall j \in [1, P(\{o_1\})], \quad Q_{ij} \neq 0 \Rightarrow C_{ij} = C'_i \end{cases}$$

Prenons un exemple. Considérons notre base de données "fil rouge" sur les employés d'une entreprise.

Avec les correspondances définies dans le schéma map1, la base comprend 44 objets ($q = 44$) instances de 11 classes différentes. Sept d'entre elles sont principales ($N = 7$).

La hiérarchie de la classe Employé en comprend cinq; aucune ne possède de champ de type "ensemble" ou "calcul multi-n-uplets". Son coût de chargement est cinq ($C'_{Employé} = 5$). La classe Département n'a pas de sous-classe mais quatre champs "ensemble" et un champ "calcul multi-n-uplets". Son coût de chargement est six ($C'_{Département} = 6$). Suit un tableau récapitulatif de l'ensemble des coûts $C'_i$ de chargement des différentes classes principales :

<footnote n="2">²et du choix du chargeur, bien sûr.</footnote>
$$ \begin{pmatrix} C'_{\text{Employé}} \\ C'_{\text{Département}} \\ C'_{\text{Projet}} \\ C'_{\text{Adresse}} \\ C'_{\text{Véhicule}} \\ C'_{\text{Software}} \\ C'_{\text{Hardware}} \end{pmatrix} = \begin{pmatrix} 5 \\ 6 \\ 3 \\ 1 \\ 1 \\ 1 \\ 1 \end{pmatrix} $$

Prenons comme chargeur l'objet employé nommé scott.

La fonction load-deep-object est appelée la première fois avec un défaut d'objet représentant scott.

Les matrices $C(\{\text{scott}\})$ et $Q(\{\text{scott}\})$ valent :

$$ C(\{\text{scott}\}) = \begin{pmatrix} 5 & 5 & 5 & 0 & 5 & 0 & 5 & 0 \\ 0 & 6 & 0 & 6 & 0 & 6 & 0 & 0 \\ 0 & 3 & 0 & 3 & 0 & 0 & 0 & 0 \\ 0 & 1 & 1 & 1 & 0 & 1 & 0 & 1 \\ 0 & 1 & 1 & 1 & 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \end{pmatrix} \qquad Q(\{\text{scott}\}) = \begin{pmatrix} 1 & 1 & 5 & 0 & 4 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 & 0 & 1 & 0 & 0 \\ 0 & 1 & 0 & 2 & 0 & 0 & 0 & 0 \\ 0 & 1 & 1 & 5 & 0 & 4 & 0 & 1 \\ 0 & 1 & 1 & 5 & 0 & 4 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \end{pmatrix} $$

Finalement,

$$ P(\{\text{scott}\}) = 8, \quad C''(\{\text{scott}\}) = 61 \quad \text{et} \quad C''' = 113 $$

Le choix du chargeur influe énormément sur le coût du chargement récursif. Le coût est en général d'autant plus faible que le chargeur est pertinent (!) et qu'il est composé d'un choix judicieux d'objets. À titre de comparaison, voici un exemple de coût de chargement où le chargeur est composé des trois objets Département {accounting, research, sales} :

$$ C(\{\text{acc., res., sales}\}) = \begin{pmatrix} 0 & 5 & 0 \\ 6 & 0 & 0 \\ 0 & 0 & 3 \\ 0 & 0 & 1 \\ 0 & 0 & 1 \\ 0 & 1 & 0 \\ 0 & 1 & 0 \end{pmatrix} \qquad Q(\{\text{acc., res., sales}\}) = \begin{pmatrix} 0 & 12 & 0 \\ 3 & 0 & 0 \\ 0 & 0 & 3 \\ 0 & 0 & 12 \\ 0 & 0 & 11 \\ 0 & 2 & 0 \\ 0 & 1 & 0 \end{pmatrix} $$

$$ P(\{\text{acc., res., sales}\}) = 3, \quad C''(\{\text{acc., res., sales}\}) = 18 $$

Le coût 18 obtenu est optimum (à comparer à $C''' = 113$) puisque chaque classe n'est interrogée qu'une seule fois.

Il faut noter que ce nombre majore en réalité le coût réel de l'opération puisque les coûts de chargement des différentes hiérarchies, qui varient en fonction des ensembles d'objets à charger, sont eux-mêmes majorés par les valeurs qui leur ont été données.
### 4.1.4 Les requêtes de consultation

#### 4.1.4.1 Représentation des requêtes de consultation

DRIVER demande au SGBD les valeurs des objets relationnels instances d'une classe au moyen d'une requête de consultation de données. La requête propre à chaque classe est construite après les déclarations de correspondances ou au premier chargement d'objets de la classe, et est ensuite réutilisée pour les suivants. Comme nous l'avons vu, elle est restreinte à un groupe d'objets particulier en la complétant d'un opérateur de filtrage.

Une requête de consultation est représentée en `DRIVER` par un objet de la classe `Driver-select`. Sa structure est particulièrement bien adaptée pour construire incrémentalement la requête et générer ensuite son expression en SQL [ANSI86].

Nous n'allons pas ici détailler la syntaxe du langage SQL. Le lecteur peu familier de ce langage pourra en trouver une description complète dans de nombreux ouvrages, par exemple dans [Mir90].

```sql
SELECT [ALL | DISTINCT] target_list
FROM table(s)
[WHERE search_cond]
[GROUP BY col(s)]
[HAVING search_cond]
[UNION subselect]
[ORDER BY col(s)]
```

<caption>Figure 4.8 : Syntaxe d'une requête SQL de consultation de données</caption>

La syntaxe générale de la requête SQL de consultation est donnée figure 4.8. Disons, en quelques mots, que la requête est introduite par le mot-clé `SELECT`, qu'elle s'adresse à un ensemble de tables et reçoit en réponse une table extraite de cet ensemble. Les colonnes de cette table extraite sont les attributs et expressions déclarées dans la `target_list` et ses n-uplets sont formés des valeurs des colonnes qui vérifient les contraintes exprimées dans la `search_cond`.

Des exemples de requêtes SQL de consultation des tables `emp` et `person` sont présentés figure 4.9 et les réponses peuvent être comparées aux contenus des tables correspondantes donnés en §B.5.

La classe `Driver-select` (listing 20) présente une structure dans laquelle on retrouve les principales articulations d'une requête SQL de consultation de données. Comme `Driver-select` est en fait une sous-classe de `Driver-subselect`, les différentes informations sont réparties entre les champs des deux classes.
$ sql smecidemo

INGRES TERMINAL MONITOR - Copyright (c) 1981, 1990 Ingres Corporation

INGRES SunOS Version 6.3/02 (su4.u42/05)

continue

* select ename, fname, job from emp where sal>2000;

\g

<table><thead><tr><td>ename</td><td>fname</td><td>job</td></tr></thead><tbody><tr><td>king</td><td>paul</td><td>president</td></tr><tr><td>jones</td><td>eric</td><td>manager</td></tr><tr><td>blake</td><td>harold</td><td>manager</td></tr><tr><td>clark</td><td>john</td><td>manager</td></tr><tr><td>ford</td><td>john</td><td>analyst</td></tr><tr><td>scott</td><td>pit</td><td>analyst</td></tr></tbody></table>

continue

* select emp.ename, emp.deptn, person PHONE from emp, person

* where emp.ename=person.pname and emp fname=person fname

* and person. position='married';

\g

<table><thead><tr><td>ename</td><td>deptn</td><td>phone</td></tr></thead><tbody><tr><td>jones</td><td>20</td><td>40-783539</td></tr><tr><td>clark</td><td>10</td><td>40-893723</td></tr><tr><td>king</td><td>20</td><td>40-374845</td></tr><tr><td>milller</td><td>10</td><td>40-223233</td></tr><tr><td>ward</td><td>30</td><td>40-378467</td></tr><tr><td>scott</td><td>20</td><td>44-003231</td></tr></tbody></table>

continue

* select fname, fname, position from person

* where not exists (select * from emp

* where emp.ename=person.pname and emp fname=person fname);

\g

<table><caption>Figure 4.9 : Requêtes SQL de consultation de données sous Ingres</caption><thead><tr><td>pname</td><td>fname</td><td>position</td></tr></thead><tbody><tr><td>clark</td><td>laura</td><td>married</td></tr><tr><td>ford</td><td>laura</td><td>married</td></tr><tr><td>king</td><td>anita</td><td>married</td></tr><tr><td>milller</td><td>rita</td><td>married</td></tr><tr><td>scott</td><td>helen</td><td>married</td></tr><tr><td>ward</td><td>suzie</td><td>married</td></tr></tbody></table>
<caption>Listing 20. Les classes de représentation des requêtes d'interrogation</caption>

```
(driver-defclass Driver-subselect ()
  (tables ordobjset Driver-table)
  (where objectset Driver-join0))

driver-defclass Driver-select Driver-subselect
  (distinctp      atom      symbol)
  (columns        ordobjset (Driver-attribute Driver-sql-op))
  (grouped-by     atom      symbol)
  (order-by       ordobjset Driver-order))
```

**Driver-subselect** est la classe des sous-requêtes. Une sous-requête est caractérisée en **DRIVER** par la forme "exists(select * from tables where conditions)". C'est un élément de restriction utilisé dans la partie **where** d'une autre requête.

Le symbole * qui se substitue à la **target_list** indique que les colonnes de la table résultat doivent être la totalité des attributs des tables interrogées. De ce fait, une sous-requête n'est déterminée que par deux ensembles d'informations, les tables logiques interrogées et les conditions restrictives.

**Driver-subselect** comprend les deux champs qui permettent de les recueillir. Le premier, **tables**, contient les tables interrogées et le second, **where**, les conditions, à savoir, les jointures qui lient les tables entre elles et les restrictions qui sélectionnent les n-uplets solution.

La classe **Driver-select** comprend un certain nombre de champs propres qui s'ajoutent à ceux qu'elle hérite de **Driver-subselect**. L'ensemble de ces champs rend possible l'expression de toute requête SQL que le système pourrait avoir besoin d'envoyer au SGBD.

**distinctp**, qui a valeur de booléen, précise si tous les n-uplets reçus en réponse à la requête doivent être différents.

**columns** contient la liste des attributs logiques à consulter et des expressions à calculer. Les expressions y sont représentées sous forme d'objets de classe racine **Driver-sql-op** (cf. §4.4.2). La syntaxe utilisée pour décrire ce champ indique simplement que ce dernier peut recueillir des instances des deux classes **Driver-attribute** et **Driver-sql-op**<fnref n="3" />.

**groupedp**, autre booléen, indique si la commande SQL group by doit être utilisée dans la requête. Nous verrons qu'elle est particulièrement utile dans les requêtes de lecture des champs de type "calcul multi-n-uplets" où le regroupement des n-uplets est nécessaire.

Enfin, **order-by** précise si l'ordre dans lequel sont rendus les n-uplets solutions est important. Dans l'affirmative, le champ contient un ensemble d'objets de la classe **Driver-order**. Le premier fixe l'ordre primaire, le second, l'ordre secondaire, et ainsi de suite.

<footnote n="3">³Cette syntaxe est en fait interdite. Les deux classes **Driver-subselect** et **Driver-select** sont décrites ici dans le langage objet de **DRIVER** pour des raisons de clarté. Dans l'implémentation, ces deux classes système sont décrites en MicroCeyx.</footnote>
Dans la version actuelle du système, les commandes **union** et **having** d'une requête de sélection n'ont pas de représentation objet car **DRIVER** ne les utilise pas. Leur éventuelle prise en compte future ne poserait aucun problème.

Comme de nombreuses classes de `DRIVER`, `Driver-subselect` et `Driver-select` répondent au message `generate-sql`. Leurs méthodes construisent et renvoient sous forme d'une chaîne de caractères la requête SQL correspondant à un objet.

### 4.1.4.2 Construction d'une requête de classe en lecture

Une requête de classe en lecture permet d'interroger la base sur la valeur des champs propres à cette classe d'un groupe de ses instances. Les champs hérités de classes ancêtres doivent être lus par les requêtes de ces classes.

Un objet relationnel est instance d'une classe s'il vérifie ses contraintes. Afin d'éliminer les n-uplets non concernés, on initialise, à la création de l'objet requête, le champ **where** en y plaçant un objet de classe `Driver-sql-op` qui regroupe l'ensemble des contraintes de la classe correspondante (listing 4.10). On le complète des jointures nécessaires pour atteindre les attributs contraints depuis la table principale.

L'examen des n-uplets solution doit permettre de déterminer lequel correspond à quel objet. Dans ce but, le champ **columns** est affecté de l'ensemble des attributs composant la clé de la relation principale. Chaque valeur de clé identifie un objet.

Pour leurs parts, le champ **tables** est initialisé avec cette relation principale et **distinctp** est mis à "vrai".

À titre d'exemple, la figure 4.11 présente un objet requête initialisé pour la classe **Employé**, tel qu'il est retourné par la fonction `initialise-class-select` (listing 4.10). L'objet `CCC[Employé]`<fnref n="4" />, qui regroupe les contraintes de la classe **Employé**, est seul dans **where** car tous les attributs participant à son expression (`emp.sal` et `emp.qualif`) sont dans la table principale.

Chaque champ propre à la classe est ensuite consulté. À chaque fois, l'objet requête est complété d'attributs, d'expressions et de jointures, en fonction du type du champ (cf §3.2.3). La complétion de la requête pour un champ est faite au moyen d'un envoi de message **info-for-select** sur cet objet champ. Ainsi, selon le type de correspondance, telle ou telle méthode est ainsi atteinte et exécutée :

* La correspondance relationnelle d'un champ atomique est un attribut, éventuellement complété d'une chaîne de jointures permettant de l'atteindre. L'attribut est ajouté au contenu du champ **columns** et les jointures au contenu du champ **where**. Quand un élément, attribut (logique) ou jointure, figure déjà dans un champ, il n'y est pas ajouté à nouveau. Quand une nouvelle table logique est introduite dans

<footnote n="4">⁴CCC pour Complete Class Constraint.</footnote>
<figure><img src="image_22.png" /><figcaption>Figure 4.10 : Algorithme de construction d'une requête de classe en lecture</figcaption></figure>

<table><caption>Figure 4.11 : Objet Driver-select initialisé pour la classe Employé</caption><thead><tr><th colspan="2">Objet de classe Driver-select</th></tr></thead><tbody><tr><td>tables</td><td>(emp)</td></tr><tr><td>where</td><td>(CCC[Employé])</td></tr><tr><td>distinct</td><td>t</td></tr><tr><td>columns</td><td>(emp.empno)</td></tr><tr><td>grouped-by</td><td>()</td></tr><tr><td>order-by</td><td>()</td></tr></tbody></table>
l'objet requête, table de l'attribut ou table d'un attribut utilisé dans une jointure, elle est ajoutée au contenu du champ **tables**.

Dans notre exemple, le champ **nom** de la classe **Employé** est associé à l'attribut **emp.ename** (figure 3.8). L'appel à la méthode **info-for-select** pour ce champ se traduit simplement par l'ajout de cet attribut au contenu de **columns**.

Le champ **num-ss** de la même classe est associé à l'attribut **person.ssnum** (figure 3.10). L'envoi du même message sur l'objet **num-ss** provoque l'ajout de **person.ss-num** à **columns**, de **person** à **tables** et de **J[emp→person]** à **where**.

* La méthode **info-for-select** des champs calculés mono-n-uplet est peu différente. Elle ajoute, au lieu d'un attribut, l'expression du calcul associée au champ au contenu de **columns**. Le traitement des tables et des jointures est identique.

* La correspondance d'un champ objet est une chaîne de jointures dont la dernière est une jointure objet stricte (cf. §3.2.3.2). Du fait des restrictions qui lui sont imposées, la jointure objet peut être analysée et disséquée. On en extrait les attributs qui sont comparés aux éléments de clé de la table principale jointe. Ce sont eux qui sont consultés dans la base. La méthode **info-for-select** des champs objet les ajoute au contenu de **columns**. Le reste du corps de la jointure objet, quand il existe, est ajouté au contenu du champ **where** avec les $N - 1$ premières jointures de la chaîne.

Le champ **véhicule** de la classe **Employé** est associé à la chaîne de jointures (J[emp→person] J[person→vehicle]) (figure 3.12). L'examen de la jointure objet J[person→vehicle] laisse apparaître que l'ensemble des attributs à consulter au titre de ce champ est (person.car). person.car est donc ajouté au contenu de **columns**. Aucun reliquat de la jointure objet ne subsistant et J[emp→person] étant déjà présente dans **where**, aucune nouvelle jointure n'est ajoutée au contenu de ce champ.

Le champ **chef** de la classe **Employé** est associé à l'auto-jointure J[emp→emp1 emp] (figure 3.22). Du fait de la décomposition de la jointure objet, l'appel à la méthode **info-for-select** pour ce champ se traduit par le simple ajout de l'attribut **emp.mgr** au contenu de **columns**.

* Le traitement des champs de type "ensemble" ou "calcul multi-n-uplets" est différent. Ces champs ont besoin d'une requête propre pour déterminer leurs valeurs.

Un champ de type "ensemble" est associé à une chaîne de jointures qui sélectionne un ensemble de n-uplets par objet solution, c'est-à-dire par n-uplet solution à la requête de classe. Une requête spécifique à chaque champ de type "ensemble" est nécessaire.

La correspondance d'un champ de type "calcul multi-n-uplets" détermine une valeur par objet. Cependant, les n-uplets sur lesquels portent les calculs sont différents des n-uplets solution à la requête principale. Une requête par champ de type "calcul multi-n-uplets" est également nécessaire.
<table><caption>Figure 4.12 : L'objet Driver-select du champ employés de la classe Projet</caption><thead><tr><th colspan="2">Objet de classe Driver-select</th></tr></thead><tbody><tr><td>tables</td><td>(project emproj emp)</td></tr><tr><td>where</td><td>(J[project→emp] CCC[Employé])</td></tr><tr><td>distinctp</td><td>t</td></tr><tr><td>columns</td><td>(project.projno emp.empno)</td></tr><tr><td>grouped-by</td><td>()</td></tr><tr><td>order-by</td><td>()</td></tr></tbody></table>

Les méthodes info-for-select de ces deux classes de champ ne complètent donc pas l'objet requête de la classe mais en construisent de nouveaux. Après leurs constitution, ces objets requêtes sont stockés dans les objets champs correspondants (cf. §3.3.2.4).

Prenons l'exemple du champ employés de la classe Projet. Ce champ de type "ensemble d'objets" est associé à la jointure J[project→emp] (figure 3.14). L'objet requête créé pour ce champ est initialisé pour la classe Projet. columns est complété des attributs constituant la clé de la relation principale de la classe des objets référencés, soit emp. J[project→emp] est insérée dans where. Le champ employés devant recueillir des instances de la classe Employé, l'objet CCC[Employé] est également ajouté au contenu de where. L'objet requête finalement établi par la méthode est présenté figure 4.12.

<table><caption>Figure 4.13 : L'objet Driver-select du champ moy-sal de la classe Département</caption><thead><tr><th colspan="2">Objet de classe Driver-select</th></tr></thead><tbody><tr><td>tables</td><td>(dept emp)</td></tr><tr><td>where</td><td>(CCC[Département] J[dept→emp])</td></tr><tr><td>distinctp</td><td>t</td></tr><tr><td>columns</td><td>(dept.deptno [avgsal)])</td></tr><tr><td>grouped-by</td><td>t</td></tr><tr><td>order-by</td><td>()</td></tr></tbody></table>

Les champs de type "ensemble d'atomes" sont gérés de façon similaire. La méthode diffère seulement par le fait que le champ columns n'est pas complété de la clé de la relation jointe mais de l'attribut associé au champ et qui contient les valeurs des éléments des ensembles.

Les champs de type "calcul multi-n-uplets" sont également gérés de la même façon. Ici, c'est l'expression du calcul qui est ajouté au contenu du champ where. À titre d'exemple, l'objet requête du champ moy-sal de la classe Département est présenté
figure 4.13. La correspondance de ce champ est l'expression d'un calcul qui détermine la moyenne des salaires des employés de chaque département (figure 3.21).

<table><caption>Figure 4.14 : L'objet Driver-select de la classe Employé</caption><thead><tr><th colspan="2">Objet de classe Driver-select</th></tr></thead><tbody><tr><td>tables</td><td>(emp person salgrade)</td></tr><tr><td>where</td><td>(CCC[Employé] J emp→person] J emp→salgrade])</td></tr><tr><td>distinctp</td><td>t</td></tr><tr><td>columns</td><td>(emp.empno emp.ename emp.fname person.ssnum<br> emp.job emp.mgr emp.responsible_for emp.sal<br> salgrade.grade emp.deptn person phone person.car)</td></tr><tr><td>grouped-by</td><td>()</td></tr><tr><td>order-by</td><td>()</td></tr></tbody></table>

* La méthode info-for-select des champs de type "ensemble ordonné" affecte en plus le champ order-by des ordonnateurs<fnref n="5" /> adéquats. Le premier détermine l'ordre primaire, le second l'ordre secondaire, et ainsi de suite. Les éventuelles jointures qui permettent de les atteindre sont ajoutées au contenu du champ where.

* Les méthodes des champs "Objet de classe inconnue" (object2, object2set, ordobj2set) sont pour leurs parts identiques à celles des champs atomiques correspondants (atom, atomset, ordatomset).

La figure 4.14 présente l'objet requête complet correspondant à la classe Employé, tel qu'il est retourné par la fonction class-select.

Ces objets requêtes sont ensuite exploités par la fonction ask-database (figure 4.4) pour interroger le SGBD et charger les données relationnelles pertinentes dans l'environnement. La requête SQL est obtenue en envoyant le message generate-sql à l'objet requête.

À titre d'exemples, les requêtes SQL générées pour les objets des figures 4.12, 4.13 et 4.14 sont présentées §4.1.4.4.

### 4.1.4.3 Exploitation des résultats

La fonction field-value-from-tuple détermine la valeur du champ d'un objet à l'aide de la requête et du n-uplet correspondant à l'objet :

algorithme field-value-from-tuple(Field, Select, Tuple)
soit Primary_value ← primary-field-value-from-tuple(Select, Tuple).Field;
retourner driver-loading-type-filter(Field, Primary_value);
fin

<footnote n="5">⁵Objets de la classe Driver-order.</footnote>
Une nouvelle fois, un envoi de message est effectué sur l'objet champ pour orienter le traitement des données en fonction du type du champ et de sa correspondance. La valeur "brute" retournée par la méthode `primary-field-value-from-tuple` appelée est ensuite confiée à la fonction `driver-loading-type-filter` qui la fait passer dans un filtre `driverloading` avant de la retourner :

* La méthode `primary-field-value-from-tuple` des champs atomiques est particulièrement simple. Elle consiste à retourner la valeur de l'attribut associé au champ contenue dans le n-uplet.

* Celle des champs de type "calcul mono-n-uplet" n'est pas différente : la valeur du champ est également directement lue dans le n-uplet.

<table><caption>Figure 4.15 : N-uplets solutions à la requête de la classe Employé</caption><thead><tr><th>emp</th><th>emp</th><th>emp</th><th>person</th><th>emp</th><th>emp</th><th>emp</th><th>emp</th><th>sal</th><th>salgrade</th><th>emp</th><th>person</th><th>person</th></tr><tr><th>empno</th><th>ename</th><th>fname</th><th>ssnum</th><th>job</th><th>mgr</th><th>respons.</th><th>sal</th><th>grade</th><th>deptn</th><th>phone</th><th>car</th></tr></thead><tbody><tr><td>7329</td><td>smith</td><td>john</td><td>J3847Y2</td><td>clerk</td><td>7902</td><td></td><td>800.</td><td>1</td><td>20</td><td>44-332047</td><td>1550</td></tr><tr><td>7499</td><td>allen</td><td>jack</td><td>76373Y</td><td>salesman</td><td>7698</td><td>software/743</td><td>1600.</td><td>3</td><td>30</td><td>78-383726</td><td>2004</td></tr><tr><td>7521</td><td>ward</td><td>peter</td><td>128347</td><td>salesman</td><td>7698</td><td>hardware/53</td><td>1250.</td><td>2</td><td>30</td><td>40-378467</td><td>1253</td></tr><tr><td>7566</td><td>jones</td><td>eric</td><td>B7C123</td><td>manager</td><td>7839</td><td>dept/20</td><td>2975.</td><td>4</td><td>20</td><td>40-783539</td><td>1975</td></tr><tr><td>7654</td><td>martin</td><td>georges</td><td>234FS13</td><td>salesman</td><td>7698</td><td></td><td>1250.</td><td>2</td><td>30</td><td>40-276951</td><td>3005</td></tr><tr><td>7655</td><td>james</td><td>peter</td><td>K4G262</td><td>salesman</td><td>7698</td><td></td><td>1350.</td><td>2</td><td>30</td><td>40-449323</td><td></td></tr><tr><td>7698</td><td>blake</td><td>harold</td><td>A473SE</td><td>manager</td><td>7839</td><td>dept/30</td><td>2850.</td><td>4</td><td>30</td><td>44-183211</td><td>1429</td></tr><tr><td>7782</td><td>clark</td><td>john</td><td>462SQ1</td><td>manager</td><td>7839</td><td>dept/10</td><td>2450.</td><td>4</td><td>10</td><td>40-893723</td><td>1230</td></tr><tr><td>7788</td><td>scott</td><td>pit</td><td>XA435C</td><td>analyst</td><td>7566</td><td>project/103</td><td>3000.</td><td>4</td><td>20</td><td>44-003231</td><td>1928</td></tr><tr><td>7839</td><td>king</td><td>paul</td><td>A34F4</td><td>president</td><td></td><td>project/102</td><td>5000.</td><td>5</td><td>10</td><td>40-223233</td><td>1232</td></tr><tr><td>7902</td><td>ford</td><td>john</td><td>6D3210</td><td>analyst</td><td>7566</td><td>project/101</td><td>3000.</td><td>4</td><td>20</td><td>40-374845</td><td>4328</td></tr><tr><td>7934</td><td>miller</td><td>paul</td><td>95Z0H5</td><td>clerk</td><td>7782</td><td></td><td>1300.</td><td>2</td><td>10</td><td>43-127772</td><td>1276</td></tr></tbody></table>

* La méthode des champ objet va consister à reconstruire l'identificateur relationnel de l'objet contenu (cf. §3.2.2) et à le proposer à la fonction `get-object` pour retrouver l'objet correspondant dans l'environnement. S'il n'y en a pas encore, un défaut d'objet est créé et retourné.

La figure 4.15 présente l'ensemble des n-uplets solutions à la requête de la classe Employé. Considérons par exemple la construction du champ `dpt` de l'employé `smith`, dont le n-uplet est en première position. L'attribut `emp.deptn` valant 20, l'identificateur de l'objet référencé est `dept/20`. `get-object(dept/20)` retourne l'objet correspondant.

L'identificateur construit peut ne correspondre à aucun objet de la base. Il s'agit alors d'un identificateur d'objet relationnel manquant (cf. §4.1.1.3). Un défaut d'objet est cependant toujours retourné, car `get-object` n'effectue aucune vérification d'existence sur l'objet dont on lui fournit l'identificateur.

* Les méthodes `primary-field-value-from-tuple` des champs de type "ensemble" ou "calcul multi-n-uplets" exploitent la requête propre du champ construite par la méthode `info-for-select` correspondante.
<table><thead><tr><td>project.projno</td><td>emp.empno</td></tr></thead><tbody><tr><td>101</td><td>7788</td></tr><tr><td>102</td><td>7499</td></tr><tr><td>102</td><td>7521</td></tr><tr><td>101</td><td>7566</td></tr><tr><td>103</td><td>7566</td></tr><tr><td>101</td><td>7329</td></tr><tr><td>102</td><td>7654</td></tr><tr><td>102</td><td>7698</td></tr><tr><td>103</td><td>7782</td></tr><tr><td>101</td><td>7839</td></tr><tr><td>102</td><td>7566</td></tr><tr><td>101</td><td>7902</td></tr><tr><td>103</td><td>7934</td></tr></tbody></table>

<table><caption>Figure 4.16 : N-uplets solutions aux requêtes des champs employés de la classe Projet et moy-sal de la classe Département</caption><thead><tr><td>dept.deptno</td><td>avg emp.sal)</td></tr></thead><tbody><tr><td>10</td><td>2916.667</td></tr><tr><td>20</td><td>2443.75</td></tr><tr><td>30</td><td>1660.</td></tr></tbody></table>

Lors du chargement d'un groupe de *P* objets d'une classe comportant un tel champ, la méthode *primary-field-value-from-tuple* correspondant à ce type de champ est appelée *P* fois.

Au premier appel, la requête est adressée au SGBD et la réponse est traduite sous forme d'un dictionnaire : les clés sont les identificateurs relationnels et les valeurs sont les valeurs du champ pour les objets correspondant aux identificateurs. Une fois construite, le dictionnaire est stocké dans l'objet champ et la valeur du champ du premier objet traité est retournée.

Aux appels suivants, le système ne fait plus que rechercher dans le dictionnaire la valeur du champ correspondant à l'objet en cours de traitement.

Reprenons comme exemples les champs employés de la classe Projet et moy-sal de la classe Département.

Les n-uplets solutions à leurs requêtes respectives (requêtes et objets requêtes présentés en §4.1.4.2) sont donnés figure 4.16. À partir de ces résultats sont construits les dictionnaires d'accès aux valeurs des champs présentées figure 4.17.

Une fois ces dictionnaires construits, l'accès à la valeur d'un champ à partir de l'identificateur relationnel de l'objet est immédiat.

* Les différents types de champs "Objet de classe inconnue" sont gérés de la même façon que les types atomiques correspondants. Seul diffère leur filtre de chargement. Ce dernier fait passer les identificateurs relationnels dans la fonction get-object pour retrouver dans l'environnement l'objet correspondant.
<table><thead><tr><th>clés</th><th>valeurs</th></tr></thead><tbody><tr><td>project/101</td><td>{Object(7788), Object(7566), Object(7329), Object(7839), Object(7902)}</td></tr><tr><td>project/102</td><td>{Object(7499), Object(7521), Object(7654), Object(7698), Object(7566)}</td></tr><tr><td>project/103</td><td>{Object(7566), Object(7566), Object(7782), Object(7934)}</td></tr></tbody></table>

<table><caption>Figure 4.17 : Dictionnaires d'accès aux valeurs des champs employés de la classe Projet et moy-sal de la classe Département</caption><thead><tr><th>clés</th><th>valeurs</th></tr></thead><tbody><tr><td>dept/10</td><td>2916.667</td></tr><tr><td>dept/20</td><td>2443.75</td></tr><tr><td>dept/30</td><td>1660.</td></tr></tbody></table>

#### 4.1.4.4 Optimisation des requêtes de sélection

L'objet CCC[Employé] regroupe les contraintes de la classe Employé. L'expression SQL qui lui correspond est :

((emp.sal >= 700. and emp.sal <= 9999.) or (emp.job = 'salesman' and (emp.sal >= 700. and emp.sal <= 9999.)) or (emp.job in ('manager','president') and (emp.sal >= 700. and emp.sal <= 9999.)) or (emp.job = 'president' and (emp.sal >= 700. and emp.sal <= 9999.)) or (emp.job = 'manager' and (emp.sal >= 700. and emp.sal <= 9999.)));

Ce genre de restriction SQL est extrêmement coûteuse en temps et en mémoire pour le SGBD qui doit la traiter. Pour sa part, Ingres refuse même de la résoudre et provoque une erreur "requête trop complexe".

DRIVER possède un optimisateur de restriction qui permet de réduire singulièrement ce genre d'expression. Les réductions sont obtenues au moyen des règles d'équivalence présentées dans la figure 4.18.

Considérons par exemple la contrainte CCC[Employé] ci-dessus. Son expression peut être exprimée sous la forme :

$$A \lor (B \land A) \lor (C \land A) \lor (D \land A) \lor (E \land A)$$

Par applications de la seule règle (7), on constate qu'elle est équivalente à : A.

Les simplifications ne sont pas toujours aussi directes, notamment quand les contraintes sont redéfinies au niveau des sous-classes. Cependant, du fait du mécanisme d'héritage, on arrive souvent à réduire les expressions de façon très importante.

Après éventuelles simplifications, les requêtes SQL générées pour les objets des figures 4.12, 4.13 et 4.14 sont respectivement :
$$C_1 \wedge C_2 = C_2 \wedge C_1 \text{ et } C_1 \vee C_2 = C_2 \vee C_1 \quad (1) \text{ et } (2)$$

$$(\bigwedge_{i}^{N} C_i) \vee [Expression] = \bigwedge_{i}^{N} (C_i \vee [Expression]) \quad (3)$$

$$Not(\bigwedge_{i}^{N} C_i) = \bigvee_{i}^{N} Not(C_i) \quad (4)$$

$$Not(\bigvee_{i}^{N} C_i) = \bigwedge_{i}^{N} Not(C_i) \quad (5)$$

$$C_1 \wedge \dots \wedge C_{N-1} \wedge (C'_1 \vee \dots \vee C_k \vee \dots \vee C'_N) = C_1 \wedge \dots \wedge C_{N-1} \quad \text{avec } k \in [1, \dots, N-1] \quad (6)$$

$$C_1 \vee \dots \vee C_{N-1} \vee (C'_1 \wedge \dots \wedge C_k \wedge \dots \wedge C'_N) = C_1 \vee \dots \vee C_{N-1} \quad \text{avec } k \in [1, \dots, N-1] \quad (7)$$

$$\bigvee_{i}^{N} (C = value_{i}) = C \in \{value_{i}, i = 1, \dots, N\} \quad (8)$$

<caption>Figure 4.18 : Règles d'équivalence utilisées pour la réduction des restrictions SQL</caption>

* select distinct project.projno, emp.empno
  from project, emproj, emp
  where project.projno = emproj.projno and
        emproj.empno = emp.empno and
        (emp.sal >= 700. and emp.sal <= 9999.);

* select distinct dept.deptno, avg emp.sal)
  from dept, emp
  where dept.deptno > 0 and dept.deptno = emp.deptno
  group by dept.deptno;

* select distinct emp.empno, emp.ename, emp.fname, person.ssnum,
    emp.job, emp.mgr, emp.responsible_for, emp.sal, salgrade.grade,
    emp.deptn, person phone, person.car
  from emp, person, salgrade
  where (emp.sal >= 700. and emp.sal <= 9999.) and
        (emp.ename = person.pname and emp.fname = person.fname) and
        (emp.sal >= salgrade.losal and emp.sal <= salgrade.hisal);
# 4.2 Écriture des objets dans la base

## 4.2.1 Algorithme général d'écriture

### 4.2.1.1 Généralités

L'écriture<fnref n="6" /> d'un objet dans la base ne peut se produire que lors d'une validation de transaction explicitement provoquée par un appel à la primitive `driver-commit-objects`.

Cette fonction cumule en fait deux rôles distincts :

* Le premier est de permettre la validation des modifications effectuées en mémoire sur les objets persistants. Les données relationnelles correspondantes sont mises à jour et tout nouvel accès sur la base, notamment concurrent, prend en compte les modifications.

* Le second est d'apporter la persistance à des objets de l'environnement, à condition toutefois que leurs classes soient connues du schéma de correspondances courant. Cette opération se traduit cette fois par l'insertion dans la base de nouvelles données relationnelles.

**driver-commit-objects** concilie ces deux rôles car il est possible de lui préciser en argument l'ensemble des objets à valider dans la base, qu'il s'agisse d'objets déjà persistants ou non.

Cette possibilité permet de réduire la granularité de la transaction `DRIVER` à un objet. Son intérêt majeur est l'efficacité accrue qu'elle procure lors des validations de transaction.

Quand les objets à valider sont précisés, deux nouvelles possibilités s'offrent à l'utilisateur :

* Sont non seulement sauvés dans la base les objets indiqués, mais également l'ensemble des objets qu'ils référencent directement ou indirectement.

* Ne sont sauvés que les objets précisés.

Cependant, dans ce second cas, sont également écrits dans la base les objets non encore persistants directement référencés par l'un au moins des objets à valider. Si l'un d'entre eux référence lui-même un nouvel objet non persistant, ce dernier est également écrit dans la base, et ainsi de suite.

Ces écritures induites sont nécessaires pour reporter fidèlement dans la base de données les objets précisés. Les clés des objets référencés doivent être déterminées pour que leurs valeurs puissent être correctement reportées dans les n-uplets des objets à écrire dans la base. Si cela n'était pas fait, les champs de type objet correspondants devraient être considérés comme vides, seraient écrits comme tels, et seraient réellement vides lors d'une nouvelle consultation.

<footnote n="6">⁶Par définition, nous différencions écrire d'insérer et mettre à jour. Écrire peut être l'un et/ou l'autre. Insérer implique l'idée d'ajout, de nouveauté, et mettre à jour, de modification de l'existant.</footnote>
<figure><img src="image_23.png" /><figcaption>Figure 4.19 : Algorithme général d'écriture d'un ensemble d'objets</figcaption></figure>
L'algorithmme de driver-commit-objects est présenté figure 4.19. Le premier argument est l'éventuelle liste des objets à valider. Le second est un booléen qui précise le mode de validation choisi quand des objets sont précisés. Ces deux arguments sont transmis à objects-to-save qui détermine l'ensemble complet des objets à écrire dans la base :

algorithme objects-to-save(Objects, Deep)

si Objects = ⊤

alors retourner all-persistent-objects-in-memory()

sinon si Deep = ⊤

alors retourner driver-all-linked-objects(Objects)

sinon retourner Objects;

fin

Si *Objects* vaut ⊤, tous les objets persistants chargés dans l'environnement sont pris en compte. Quand un ensemble d'objets est précisé, le deuxième argument détermine si ces objets sont les seuls à être considérés. Si *Deep* est vrai, les objets référencés par ceux qui sont indiqués sont également à écrire dans la base; dans le cas contraire, seuls les objets indiqués sont retenus.

### 4.2.1.2 Principe de l'algorithmme général d'écriture

Le principe de l'algorithmme général d'écriture est le suivant :

Dans un premier temps sont consultées dans la base les données relationnelles correspondant aux objets sélectionnés. Par comparaison entre ces données et les objets en mémoire vont pouvoir être identifiés ceux qui ont subi des modifications et ceux qui n'ont pas encore de correspondance relationnelle.

Les objets sont ensuite examinés un par un. Selon les cas sont effectuées dans la base des mises-à-jour ou des insertions de nouveaux n-uplets relationnels. Parfois, mises-à-jour et insertions sont conjointement réalisées pour un même objet (cf. §4.2.3).

Afin de limiter le coût de consultation des données relationnelles, les instances de chaque classe sont d'abord déterminées et regroupées. Chaque classe n'est interrogée qu'une seule fois pour l'ensemble de ses instances. L'interrogation n'est bien sûr faite que si elle est pertinente, c'est-à-dire si la classe possède au moins un objet déjà persistant parmi ceux à écrire.

La consultation préalable de la valeur des objets dans la base avant leurs éventuelles mises-à-jour est indispensable (cf. §4.2.3. On peut d'ores et déjà souligner qu'elle est très rentable en termes de coûts en requêtes car elle permet de minimiser le nombre de requêtes d'écriture adressées à la base. Pour un surcoût de (1 + *N*) requêtes de consultation par classe<fnref n="7" /> (cf. §4.1.2.1) est déterminé l'état des objets avant leurs éventuelles écritures dans

<footnote n="7">⁷*N* est le nombre de champs de type "ensemble" de la classe.</footnote>
la base. Grâce à cette connaissance, le SGBD ne reçoit de requêtes d'insertion ou de mise-
à-jour qu'à bon escient. Si, par exemple, aucun objet n'a été modifié depuis la dernière
validation, aucune nouvelle requête n'est adressée à la base.

### 4.2.1.3 Détails de l'algorithme

commit-objects initialise trois dictionnaires donnant accès à diverses informations utilisées
à différents stades du processus d'écriture :

* Le premier, appelé **saving-buffer**, est un dictionnaire donnant accès à des objets
système de la classe **Driver-saving** (cf. §4.2.3.2). Chacun d'entre eux contient des
informations relatives à l'écriture de l'un des objets en cours de sauvegarde. La clé
permettant d'accéder à chacun d'entre eux dans ce dictionnaire est d'ailleurs l'objet
en cours de sauvegarde qui leur correspond.

initialise-saving-buffer prépare un dictionnaire vide qui sera complété ultérieurement.

* Le second, appelé **tuple-buffer**, est le dictionnaire des n-uplets relationnels cor-
respondant aux objets déjà persistants en cours de validation. Ce dictionnaire est
à deux niveaux. Au premier, les clés sont les classes; elles donnent accès à des
sous-dictionnaires appelés **class-tuple-buffer**. Chaque sous-dictionnaire contient les
n-uplets des objets qui sont au moins instances de la classe correspondante. Ces
n-uplets sont la réponse du SGBD à la requête principale de la classe, restreinte aux
objets sélectionnés (cf. §4.1.2.1). Dans un sous-dictionnaire, le n-uplet d'un objet y
est accessible par la clé relationnelle de l'objet correspondant.

initialise-tuple-buffer prépare également un dictionnaire vide qui sera complété ulté-
rieurement.

* La dernière, appelée **object-buffer**, est le dictionnaire des objets à écrire. Ici, les
clés d'accès sont les différentes classes décrites dans le schéma de correspondances
courant, et les valeurs, les objets à écrire qui en sont au moins instances. Ce dic-
tionnaire constitue aussi un indicateur sur le degré d'avancement de l'écriture. En
effet, un objet validé dans la base ou en cours de validation en a été enlevé. À tout
moment n'y figurent que ceux qui sont en attente d'être écrits. La validation globale
est terminée quand le dictionnaire est vide d'objet.

build-object-buffer initialise et construit le dictionnaire. Après cette construction,
aucun nouvel objet n'y est généralement ajouté, sauf en cas d'incohérence dans le
réseau d'objets à écrire (cf. §4.2.2.2).

L'appel à check-class-constraints-on-objects permet ensuite de vérifier que les objets
respectent effectivement les contraintes imposées par leur classe. Si ce n'est pas le cas,
selon la valeur de l'indicateur driver-writing-help, une erreur est générée ou une modification
pertinente de l'objet est proposée.

Cet examen est important. Il garantit qu'après écriture, un nouveau chargement dans
l'environnement de ces objets les conduira à être de la même classe que celle qui est la
leur au moment de l'écriture.
On ignore si le modèle objet client utilisé gère les contraintes de classes et s'il est en mesure d'effectuer lui-même ces vérifications. Pour cette raison, `DRIVER` ne peut pas se dispenser d'effectuer lui-même ce contrôle.

Il est ensuite procédé à l'écriture des instances de chaque classe au moyen d'appels à `commit-class-objects`.

Une fois toutes les écritures terminées peuvent être définitivement validées les clés relationnelles des objets nouvellement persistants. Cette validation n'est faite qu'au dernier moment de façon à éviter toute rémanence d'allocation de clé en cas d'erreur lors de l'exécution de `driver-commit-objects`, auquel cas `DRIVER` doit restaurer l'environnement et la base dans l'état où ils étaient lorsque `driver-commit-objects` a été exécuté (cf. §4.2.6).

### `commit-class-objects`

Le rôle de `commit-class-objects` est de procéder à l'écriture de toutes les instances d'une classe donnée. Elle retourne en résultat la liste des clés relationnelles des objets correspondants.

Dans un premier temps, le sous-dictionnaire `class-tuple-buffer` de la classe est construite à l'aide de `build-class-tuple-buffer`.

Une fois le sous-dictionnaire des n-uplets construit, chaque instance de la classe se trouvant encore dans le dictionnaire `Class_object_buffer` est ensuite confiée à `commit-object` pour écriture. L'appel à `next-object` retourne l'objet suivant dans le dictionnaire et l'en retire. Ainsi, si `commit-class-objects` est appelée récursivement pour la même classe, l'objet en question ne se trouve plus dans le dictionnaire et n'est donc pas considéré à nouveau.

`get-object-tuple` recherche le n-uplet correspondant à l'objet traité dans le `class-tuple-buffer` de la classe considérée.

Si un n-uplet est retrouvé, l'objet est au moins instance de cette classe dans la base. `commit-object` oriente donc la suite de l'exécution vers une éventuelle mise-à-jour de l'objet dans la base en faisant appel à `update-object`.

Dans le cas contraire, une alternative apparaît :

* L'objet n'a pas encore de représentation relationnelle dans la base. Il doit donc y être inséré.

* L'objet relationnel n'est pas de la classe considérée dans la base. Deux raisons d'échec à la requête de consultation existent :

- La première est que l'objet relationnel ne vérifie tout simplement pas les contraintes imposées par la classe. Une simple mise-à-jour de l'objet dans la base s'impose alors.

- La seconde raison peut être qu'une jointure non utilisée dans les correspondances des classes ancêtres permet d'atteindre, dans une table secondaire, la correspondance d'un champ propre à la classe. Si l'objet relationnel n'a jamais
été instance jusqu'à présent de la présente classe, la jointure ne peut qu'échouer. Dans ce cas de figure, l'écriture de l'objet dans la base se traduit par des insertions de n-uplets dans les tables secondaires nouvellement atteinte et des mises-à-jour des n-uplets qu'on pouvait déjà atteindre au niveau des classes ancêtres (cf. §4.2.3).

On constate ici que l'absence de n-uplet dans la réponse à une requête de classe ne signifie pas nécessairement que l'objet correspondant n'était pas persistant avant l'écriture. **DRIVER** détermine si un objet donné est persistant en consultant un dictionnaire des objets persistants qu'il gère dans chaque schéma de correspondances (cf. §3.3.2.1). Dans ce dictionnaire, un objet est accessible par sa clé relationnelle.

Quand aucun n-uplet n'est retourné par la base, **write-object** est appelé.

À titre d'exemple, créons dans l'environnement un nouvel objet **Vendeur**. Appelons-le **johnson**. En tant que vendeur, il dépend du département **sales** et son chef est **blake**. Comme il est nouveau dans l'entreprise, on demande à un autre vendeur **james** de l'encaadrer dans un premier temps. Cette responsabilité est officialisée en plaçant l'objet **johnson** dans le champ **responsable-de** de **james**. Le surplus de travail engendré est compensé par une majoration de 500. de la commission qu'il touche. On profite enfin de l'occasion pour rectifier son numéro de téléphone qui a récemment changé et préciser qu'il habite maintenant à la même adresse que son collègue **martin**. De ce fait, il utilise également sa voiture.

La figure 4.20 présente la valeur de l'objet **johnson** et les valeurs du vendeur **james** avant puis après modification.

L'ordre `driver-commit-objects({Object("james")}, T)` déclenche l'écriture dans la base de l'objet **james** et de ceux qu'il référence.

`objects-toSave` détermine l'ensemble des d'objets à écrire, soit :

```json
{ 
  Object("james"), Object("blake"), Object("king"), Object(dept/10),
  Object(address/7839), Object(vehicle/1232), Object("sales"), Object(hardware/53),
  Object(software/743), Object(software/874), Object("allen"), Object(address/7499),
  Object(vehicle/2004), Object("ward"), Object(address/7521), Object(vehicle/1253),
  Object("martin"), Object(address/7654), Object(vehicle/3005), Object(address/7698),
  Object(vehicle/1429), Object("johnson")
}
```

Un certain nombre d'entre eux sont des défauts d'objet. On les reconnaît dans l'ensemble précédent au fait qu'ils sont présentés référencés par une clé relationnelle alors que les objets jumeaux sont référencés par leurs noms entre guillemets (" "). Un objet encore représenté par un défaut ne peut pas avoir été modifié en mémoire. Pour cette raison, les défauts d'objet ne sont pas pris en compte et ne figurent pas dans les dictionnaires.
<figure><img src="image_24.png" /></figure>

<figure><img src="image_25.png" /><figcaption>Figure 4.20 : Objets à mettre à jour dans la base</figcaption></figure>
Après initialisation des deux dictionnaires "saving-buffer" et "tuple-buffer" est construit l'"object-buffer", le dictionnaire des objets à écrire. Son contenu est le suivant :

<table><thead><tr><th colspan="2">Object-buffer initial pour driver-commit-objects({Object("johnson")}, T)</th></tr><tr><th>Clés</th><th>Valeurs</th></tr></thead><tbody><tr><td>Employé</td><td>{ Object("james"), Object("blake"), Object("king"), Object("allen"), Object("ward"), Object("martin"), Object("johnson") }</td></tr><tr><td>Vendeur</td><td>{ Object("james"), Object("allen"), Object("ward"), Object("martin"), Object("johnson") }</td></tr><tr><td>Cadre</td><td>{ Object("blake"), Object("king") }</td></tr><tr><td>Président</td><td>{ Object("king") }</td></tr><tr><td>Chef-de-service</td><td>{ Object("blake") }</td></tr><tr><td>Département</td><td>{ Object("sales") }</td></tr><tr><td>Projet</td><td>{ }</td></tr><tr><td>Adresse</td><td>{ }</td></tr><tr><td>Véhicule</td><td>{ }</td></tr><tr><td>Software</td><td>{ }</td></tr><tr><td>Hardware</td><td>{ }</td></tr></tbody></table>

Il est ensuite vérifié que les objets respectent les contraintes imposées par leurs classes. En examinant les instances de la classe Employé, DRIVER détecte que la valeur du champ salaire de l'objet johnson ne vérifie pas la contrainte qui lui est imposée (salaire compris entre 700. et 9999.). Si l'indicateur système driver-writing-help vaut T, une nouvelle valeur est demandée à l'utilisateur et est affectée au champ. Choisissons par exemple la valeur 1000.

En examinant les instances de la classe Vendeur, DRIVER détecte à nouveau qu'un champ de l'objet johnson ne vérifie pas la contrainte qui lui est imposée. Il s'agit cette fois du champ qualif, auquel il est imposé de valoir salesman. Une nouvelle fois, en supposant que driver-writing-help vaille T, l'utilisateur est mis à contribution pour indiquer une valeur compatible avec la contrainte imposée. La seule valeur possible est salesman.

A priori, DRIVER ne détecte plus ensuite aucun problème. La valeur de l'objet johnson avant écriture est finalement :

<table><thead><tr><th colspan="4">Objet de classe Vendeur</th></tr></thead><tbody><tr><td>nom</td><td>johnson</td><td>dpt</td><td>Object("sales")</td></tr><tr><td>prénom</td><td>()</td><td>adresse</td><td>()</td></tr><tr><td>num-ss</td><td>()</td><td>tel-privé</td><td>()</td></tr><tr><td>qualif</td><td>salesman</td><td>véhicule</td><td>()</td></tr><tr><td>chef</td><td>Object("blake")</td><td>situ-famille</td><td>()</td></tr><tr><td>responsable-de</td><td>()</td><td>commission</td><td>()</td></tr><tr><td>salaire</td><td>1000.</td><td>sal-total</td><td>1000.</td></tr><tr><td>grade</td><td>()</td><td></td><td></td></tr></tbody></table>
On peut dès à présent signaler que les modifications faites aux objets PENDANT la validation de transaction (comme les modifications précédentes des champs **salaire** et **qualif**) ne seront définitives que si l'écriture se termine normalement. En cas d'erreur ou d'interruption de l'écriture provoquée par l'utilisateur, toute modification éventuellement effectuée sur les objets est défaite de façon à restituer l'environnement tel qu'il était avant le début de la validation (cf. §4.2.5 et §4.2.6).

Le premier **class_object_buffer** non vide est celui de la classe **Employé**. Ce sera donc le premier à être confié à **commit-class-objects**. Le sous-dictionnaire des n-uplets de la classe **Employé** qui n'était pas encore établi est construit. Sa valeur est :

<table><thead><tr><th colspan="2">Tuple-buffer de la classe Employé</th></tr><tr><th>Clés</th><th>Valeurs</th></tr></thead><tbody><tr><td>emp/7499</td><td>[7499 allen jack 76373Y salesman 7698 software/743 1600. 3 30 78-383726 2004]</td></tr><tr><td>emp/7521</td><td>[7521 ward peter 128347 salesman 7698 hardware/53 1250. 2 30 40-378467 1253]</td></tr><tr><td>emp/7654</td><td>[7654 martin georges 234FS1 salesman 7698 () 1250. 2 30 40-276951 3005]</td></tr><tr><td>emp/7655</td><td>[7655 james peter K4G262 salesman 7698 () 1350. 2 30 40-449323 ()]</td></tr><tr><td>emp/7698</td><td>[7698 blake harold A473SE manager 7839 dept/30 2850. 4 30 44-183211 1429]</td></tr><tr><td>emp/7839</td><td>[7839 king paul A34F4 president () () 5000. 5 10 40-223233 1232]</td></tr></tbody></table>

Chaque objet contenu dans le **class_object_buffer** est ensuite confié à **commit-object** pour écriture. Juste avant l'appel, l'objet traité est retiré du **class_object_buffer**.

Mis à part **johnson**, **DRIVER** a pu retrouver dans la base le n-uplet de chaque objet contenu dans le buffer. L'écriture de chacun d'entre eux est donc réalisée par un appel à **update-object**, exceptée celle de **johnson**, réalisée en ce qui la concerne par un appel à **write-object**.

À chaque fois, la clé relationnelle retournée par **commit-object** est recueillie. La liste de ces clés est finalement restituée par **commit-objects**.

Chaque **class_object_buffer** est ainsi traité. Une fois l'object-buffer vide d'objets, l'écriture est terminée.

Nous allons maintenant détailler les algorithmes de mise-à-jour et d'insertion d'objet dans la base. Nous reprendrons et poursuivrons alors l'exemple précédent pour les illustrer.
**algorithme** update-object(Class, Object, Tuple)

**soit** Select ← class-select(Class)

et Key ← key-from-select(Select, Tuple)

et Object_operator ← build-object-filter(Class, {Key})

et Updates ← make-object-updates(Select, Object_operator);

**faire** build-updates(Updates, Class, Object, Tuple)

et do-writings-if-any(Updates);

**retourner** Key;

fin

**algorithme** build-updates(Updates, Class, Object, Tuple)

**pour tout** F ∈ fields(CLASS)

**faire si** mappedp.F = ⊤

et read-only.F = ⊥

**alors faire** info-for-update(Updates, Object, Tuple).F;

fin

**algorithme** do-writings-if-any(Writings)

**pour tout** W ∈ Writings

**faire si** info-to-writep(W) = ⊤

**alors faire** write-database().W;

fin

<caption>Figure 4.21 : Algorithme de mise-à-jour d'un objet dans la base</caption>
### 4.2.2 Mise-à-jour des objets dans la base

#### 4.2.2.1 Principe de l'algorithme

L'algorithme update-object (figure 4.21) permet de reporter dans la base les éventuelles modifications effectuées dans l'environnement sur un objet jumeau. Il permet aussi de redonner à un objet relationnel dont l'objet jumeau a été construit une valeur qu'il a perdue à la suite d'une modification concurrente.

update-object est appliqué à un objet jumeau pour une classe donnée. À chaque appel ne sont considérés de l'objet que les champs propres à la classe donnée. Ne sont donc prises en compte que les modifications effectuées sur ceux-là. L'examen des autres champs est fait lors d'autres appels à update-object ou write-object, où la classe traitée est celle à laquelle ils appartiennent.

Avant la consultation des champs sont créés et initialisés un certain nombre d'objets Driver-update. Les instances de la classe Driver-update sont la représentation en DRIVER des requêtes de mise-à-jour (cf. §4.2.2.3). Une requête SQL de mise-à-jour ne pouvant s'adresser qu'à une table spécifique, sont préparés autant d'objets Driver-update que la classe comporte de tables élémentaires. Ainsi, lors de l'examen d'un objet, si deux champs modifiés ont une correspondance portant sur deux tables différentes, deux requêtes seront nécessaires pour reporter les modifications dans la base.

Les champs sont ensuite consultés les uns après les autres. Par comparaison entre leurs valeurs dans l'objet et leurs valeurs déduites du n-uplet relationnel, on détermine ceux qui ont été modifiés dans l'environnement ou dans la base. Quand une différence est détectée, l'objet Driver-update correspondant est complété.

Une fois tous les champs consultés, une mise-à-jour de la base est réalisée si nécessaire par la fonction do-writings-if-any. Chaque objet Driver-update est examiné. Quand l'un d'entre eux comporte une information à reporter dans la base, l'envoi de message write-database est effectué sur cet objet et se traduit par l'envoi au SGBD de la requête SQL appropriée.

#### 4.2.2.2 Méthodes de mise-à-jour des différentes classes de champ

Un champ qui possède une correspondance et qui est autorisé à écrire dans la base est consulté par un envoi de message info-for-update. Selon sa classe, telle ou telle méthode est atteinte et exécutée.

La plupart d'entre elles sont présentées figure 4.22. Seules manquent celles des champs de type "ensemble". Leur étude sera présentée ultérieurement (cf. §4.2.4). Disons seulement que comme le traitement des champs de type "ensemble" est identique lors de l'insertion ou de la mise-à-jour d'un objet dans la base, les méthodes en insertion et en mise-à-jour sont également les mêmes.
algorithme Driver-field::info-for-update(Updates, Object, Tuple).Field
fin

algorithme Atom-field::info-for-update(Updates, Object, Tuple).Field
soit Select ← class-selectDocument Field
et Env_field_value ← field-value(Field, Object)
et Db_field_value ← field-value-from-tuple(F, Select, Tuple);
si Env_field_value ≠ Db_field_value
alors soit Update ← find-writing(table.attr Field, Updates)
  et Attr_value ← attr-value-to-database(attr Field, Field, Object);
  faire add-into-writing(attr Field, Attr_value, Update);
fin

algorithme Object2-field::info-for-update(Updates, Object, Tuple).Field
soit Select ← class-selectDocument Field
et Env_contained_object ← field-value(Field, Object)
et Db_contained_object ← field-value-from-tuple(F, Select, Tuple);
si Env_contained_object ≠ Db_contained_object
alors soit Update ← find-writing(table.attr Field, Updates)
  et Attr_value ← do-get-object-key(Env_contained_object, {}, {}, Field);
  faire add-into-writing(attr Field, Attr_value, Update);
fin

algorithme Object-field::info-for-update(Updates, Object, Tuple).Field
soit Select ← class-selectDocument Field
et Env_contained_object ← field-value(Field, Object)
et Db_contained_object ← field-value-from-tuple(F, Select, Tuple);
si Env_contained_object ≠ Db_contained_object
alors soit Table ← joined-table(joins Field)
  et Update ← find-writing(Table, Updates)
  et Key_attrs ← key.deftable.table.class(Field)
  et Key ← contained-object-key(Object, Field, Select, Tuple);
pour tout Attr ∈ Key_attrs
  soit Attr_value ← attr-value-from-key(Attr, Key_attrs, Key);
  faire add-into-writing(Attr, Attr_value, Update);
fin

<caption>Figure 4.22 : Méthodes info-for-update</caption>
Toutes les méthodes déterminent la valeur du champ dans l'objet relationnel et la com-
parent à la valeur du même champ dans l'objet jumeau. Si ces valeurs sont identiques, il
n'y a rien à faire; les données relationnelles qui correspondent au champ reflètent correc-
tement la valeur du champ en mémoire. Si elles diffèrent, une mise-à-jour de la base est
alors nécessaire.

La comparaison des deux valeurs est faite au sein de chaque méthode. On pourrait s'en étonner, le mécanisme permettant de les retrouver étant le même à chaque fois. En fait, c'est le test de comparaison qui change. Bien qu'il soit toujours représenté par le signe = dans les algorithmes, il désigne des tests d'égalité différents. Il exprime un test d'égalité de valeurs dans la méthode des champs atomiques, un test d'égalité de référence pour les champs "objet" et "objet 2", enfin des tests d'égalité d'ensembles pour les champs de type "ensemble". Ces derniers varient eux-mêmes selon le type de l'élément de l'ensemble.

Les méthodes **Atom-field::info-for-update** et **Object2-field::info-for-update** se ressemblent beaucoup. Quand une mise-à-jour de la base est nécessaire, elles déterminent toutes deux l'objet **Driver-update** concerné, la valeur d'attribut à insérer dans la base, et complètent pareillement l'objet **Driver-update** sélectionné.

Elles divergent par contre dans le calcul de la valeur à donner à l'attribut correspondant au champ. La méthode des champs atomiques détermine cette valeur à l'aide de la fonction **attr-value-to-database** :

algorithme attr-value-to-database(Attr, Field, Object)
soit Field_value ← field-value(Field, Object);
retourner driver-saving-type-filter(Attr, Field, Object, Field_value);
fin

La valeur du champ est confiée à la fonction driver-saving-type-filter qui la fait passer
dans un filtre driverwriting avant de la retourner.

Pour les champs de type "objet 2", la valeur donnée à l'attribut est la clé de l'objet contenu. Leur méthode établit cette clé en faisant appel à la fonction **do-get-object-key**. Si l'objet contenu est déjà persistant, **do-get-object-key** retourne simplement sa clé. Si ce n'est pas le cas, elle provoque son écriture et récupère la clé qui lui a été allouée (cf. §4.23).

La méthode des champs de type "objet" est un peu plus complexe car c'est un ensemble d'attributs qu'il faut mettre à jour dans la base quand le contenu d'un champ de ce type a été modifié.

Dans sa version actuelle, DRIVER impose à tous ces attributs d'appartenir à la même relation. Cette limitation est très peu restrictive; elle l'est d'autant moins qu'on s'aperçoit, à l'usage, que la plupart des clés utilisées dans les schémas comprennent très rarement plusieurs attributs.
**algorithme** contained-object-key(Object, Field, Select, Tuple)
**soit** Contained_object ← field-value(Field, Object)
et Object_key_attrs ← key.deftable.table.ownerclass Field
et Read_attrs ← ra-compil Field
et Constrained_attrs ← Object_key_attrs ∩ Read_attrs
et Constrained_attr_values ← attr-values-from-tuple(Constrained_attrs, Select, Tuple)
et Key ← do-get-object-key(Contained_object,
                             Constrained_attrs, Constrained_attr_values, Field);
**faire** check-contained-object-key(Key, Read_attrs,
                                   Constrained_attrs, Constrained_attr_values,
                                   (Read_attrs ⊂ Object_key_attrs), Object, Field);
**retourner** Key;
**fin**

**algorithme** check-contained-object-key(Key, Read_attrs, Const_attrs, Const_attr_values, Fixedp, Object, Field)
**tant que** compatible-object-key-p(Read_attrs, Key, Const_attrs, Const_attr_values) = ⊥
si driver-writing-help = ⊥
**alors** soit Cont_object ← affect-new-object(Object, Field, Const_attrs, Const_attr_values, Fixedp);
faire complete-buffers-if-unread-object(Cont_object)
et Key ← do-get-object-key(Cont_object, Const_attrs, Const_attr_values, Field);
sinon faire fatal-error("Unauthorised object field value")
**fin**

**algorithme** do-get-object-key(Cont_object, Const_attrs, Const_attr_values, Field)
si ∃ Cont_object
**alors retourner** {}
sinon soit Key ← object-key-while-saving(Cont_object);
si ∃ Key
**alors retourner** Key
sinon soit Const_key_attrs ← constrained-key-attrs(Const_attrs, Field);
**retourner** save-object(Cont_object, Const_key_attrs, Const_attr_values);
**fin**

<caption>Figure 4.23 : Algorithme de contained-object-key</caption>
algorithme save-object(Object, Const_key_attrs, Const_attr_values)
    faire set-initial-pairs(Object, Const_key_attrs, Const_attr_values)
    soit Class_object_buffer ← object-main-class-object-buffer(Object);
    et Keys ← commit-class-objects(Class_object_buffer);
    retourner tete(Key);
    fin

<caption>Figure 4.24 : Algorithme de contained-object-key (suite)</caption>

Quand le contenu du champ doit être reporté dans la base, on recherche l'objet Driver-update de la table à mettre à jour. Cette table est celle qui comprend les attributs comparés aux attributs de clé de l'objet contenu puisque ce sont ces attributs qui vont être mis à jour.

L'algorithme de contained-object-key est présenté figures 4.23 et 4.24. Son rôle est de déterminer et retourner la clé de l'objet contenu.

On constate à son examen qu'il n'est pas aussi simple que ce à quoi on aurait pu s'attendre. Cette complexité est liée au traitement des jointures restreintes et des jointures fixées (cf. §3.2.3.2).

Afin d'éviter toute modification intempestive des attributs référentiels participant également à la clé de leur relation, l'algorithme de contained-object-key procède à la vérification du contenu des champs objet avant de retourner une référence.

Dans un premier temps (figure 4.23) sont déterminés les attributs référentiels "contraints", c'est-à-dire ceux participant à une clé. S'il y en a, leurs valeurs sont retrouvées dans le n-uplet de l'objet contenu. Une fois la référence de l'objet en question déterminée par la fonction `do-get-object-key`, la vérification de cohérence est faite par la fonction `check-contained-object-key`. Suite à cette vérification, la référence de l'objet est retournée.

Au sein de `check-contained-object-key`, le test de cohérence est effectué par le prédictat compatible-object-key-p. Quand ce test échoue, selon la valeur de l'indicateur `DRIVER driver-writing-help`, une erreur fatale est déclenchée ou une modification du champ objet visant à rétablir la cohérence est engagée. La procédure d'erreur fatale interrompt définitivement et annule la validation de transaction tandis qu'une modification adéquate du champ objet incriminé permet de poursuivre ensuite l'opération.

Le déclenchement d'erreur fatale est examiné en §4.2.6, la modification d'un objet pendant l'écriture en 4.2.5.

Dans le second cas, la modification du champ objet est assurée par la fonction `affect-new-object`. `affect-new-object` fait d'abord appel à la fonction `allowed-objects` (figure 4.25) pour connaître l'ensemble des objets de la couche virtuelle qui sont compatibles avec les contraintes imposées. L'utilisateur est ensuite mis à contribution pour en sélectionner un parmi eux. L'objet choisi est affecté dans le champ, est éventuellement rajouté dans
l'object-buffer et est enfin retourné.

En examinant l'algorithmme de allowed-objects (figure 4.25), on constate que l'ensemble des objets autorisés comme valeur d'un champ objet pour un objet donné varie si la jointure objet associée à ce champ est fixée et si l'objet propriétaire du champ est persistant. Dans ce cas, l'ensemble se réduit à un seul car la référence de l'objet contenu est entièrement déterminée. Cet objet est alors retourné (*Objects2*). Dans les autres cas, l'ensemble des objets autorisés est constitué de tous ceux qui vérifient les contraintes, qu'ils soient persistants ou non (*Objects1* ∪ *Objects2*).

La clé relationnelle de l'objet contenu dans le champ est déterminée par **do-get-object-key** (figure 4.23). Si le champ est vide, la fonction retourne {} (absence de valeur). S'il y a un objet, elle cherche sa clé, y compris l'éventuelle clé provisoire lui ayant été affectée au cours de la même validation de transaction. Si une telle clé existe, elle la retourne. Dans le cas contraire, l'insertion de l'objet est déclenchée. Si la jointure objet associée au champ est restreinte, les attributs clés contraints de l'objet à insérer sont déterminés et communiqués avec leurs valeurs à **save-object**. Ces informations vont permettre que l'objet contenu ait une clé relationnelle compatible avec son référençage dans le présent champ.

**save-object** (figure 4.24) déclenche la validation du *class_object_buffer* de la classe principale de l'objet considéré. *set-initial-pairs* prend en compte les éventuelles contraintes d'attributs à propager et *commit-class-objects* amorce la validation.

Illustrons l'algorithmme *update-object* complet en reprenant l'exemple de validation du vendeur *james* et des objets qu'il référence (cf. §4.2.1.3). Lors de cette validation, le premier appel à *update-object* est effectué pour l'objet *james* lors du traitement du *class_object_buffer* de la classe *Employé*.

La clé relationnelle de *james* (*emp/7655*) est retrouvée à partir de son n-uplet et utilisée pour déterminer l'opérateur relationnel de l'objet ("*emp.empno* = 7655"). Cet opérateur permettra de n'appliquer les éventuelles requêtes de mise-à-jour qu'aux n-uplets correspondant à l'objet considéré. *make-object-updates* crée deux objets *Driver-update*, un pour la table *emp* et le second pour la table *person*. Ces objets, initialement vides de modification, pourront être complétés par les méthodes *info-for-update*.

La méthode de la classe *Object2-field* est la première à identifier un champ modifié de *james*. Le champ en question est responsable-de, vide dans l'objet relationnel et référençant *johnson* dans l'objet jumeau. La modification du champ va se traduire côté relationnel par la modification de l'attribut *emp.responsible_for* associé. La valeur de cet attribut est la clé relationnelle de l'objet contenu. Comme l'objet *johnson* n'est pas encore persistant, sa clé n'est pas encore établie et **do-get-object-key** provoque son insertion dans la base par l'appel de *save-object*. Cet appel engendre une nouvelle validation du *class_object_buffer* de la classe *Employé*. En supposant que cette validation se solde par l'attribution à l'objet *johnson* de la clé *emp/7936*, l'objet *Driver-update* correspondant à *emp* est complété de la paire attribut-valeur [*emp.responsible_for*, "emp/7936"). Ajoutons enfin que la correspondance du champ ne fait pas intervenir d'attribut contraint. De ce fait, *set-initial-pairs* n'a aucune action. Nous reviendrons sur l'insertion de l'objet *johnson* et la détaillerons en §4.2.3.6.
algorithme allowed-objects(Class, Attrs, Attr_values, Fixedp)
soit Objects1 ← non-persistent-compatible-objects(Class)
et Objects2 ← persistent-compatible-objects(Class, Attrs, Attr_values);
si Fixedp = ⊤
et Objects2 ≠ {}
alors retourner Objects2;
sinon retourner Objects1 ∪ Objects2;
fin

algorithme non-persistent-compatible-objects(Class)
soit All_class_objects ← all-class-objects(Class);
et Objects ← {};
pour tout Object ∈ All_class_objects
soit Key ← object-key-while-saving(Object)
si ∃ Key
alors faire Objects ← Objects ∪ {Object};
retourner Objects;
fin

algorithme persistent-compatible-objects(Class, Attrs, Attr_values)
soit Select ← class-select(Class)
et Operator ← make-attrs/values-operator(AttrS, Attr_values)
et Tuples ← ask-database(Select, Operator)
et Objects ← {};
pour tout Tuple ∈ Tuples
soit Object ← get-object(object-key-from-select(Select, Tuple));
faire Objects ← Objects ∪ {Object};
retourner Objects;
fin

<caption>Figure 4.25 : Algorithme de allowed-objects</caption>
La méthode `Object-field::info-for-update` identifie ensuite le champ `adresse` comme ayant également été modifié dans l'environnement. Cette fois, l'objet référencé dans le champ est déjà persistant et a pour clé `address/7654`. Seulement, la jointure objet `J emp address` (de définition `emp.empno = address.empno`) qui est associée au champ est fixée car l'attribut référentiel `emp.empno` qui y participe constitue aussi la clé de la relation `emp`. Le test de compatibilité effectué par `compatible-object-key-p` échoue car la valeur `7654` d'emphnop qui serait nécessaire pour que l'objet `Adresse` référencé puisse être retenu comme valeur du champ est différente de 7655, valeur de clé de l'objet `james`. En supposant que `driver-writing-help` vaille T, `affect-new-object` détermine dans la couche virtuelle l'ensemble des objets `Adresse` compatibles avec les contraintes, `{Object(address/7655)`, et affecte au champ sa seule valeur possible. Finalement, l'objet `Driver-update` correspondant à emp est complété de la paire `[emp.empno, 7655]`.

La méthode `Atom-field::info-for-update` identifie ensuite le champ `tel-prive` comme ayant également été modifié. L'attribut correspondance du champ est `person PHONE`. L'application de la méthode complète donc l'objet `Driver-update` de `person` de la paire `[person PHONE, "40-445843"]`.

Enfin, la méthode `Object-field::info-for-update` détermine également que le champ `véhicule` a été modifié. L'objet `Véhicule` référencé dans le champ est déjà persistant et a pour clé `vehicle/3005`. L'attribut référentiel `person.car` n'était pas élément de clé, il n'est pas contraint. `contained-object-key` ne détecte a fortiori aucune incohérence et retourne la clé de l'objet contenu. L'objet `Driver-update` correspondant à `person` est complété de la paire `[person.car, 3005]`.

`véhicule` est le dernier champ de `james` propre à la classe `Employé`. Son traitement termine l'exécution de `build-updates`. L'appel à `do-writings-if-any` qui suit se traduit par l'envoi au SGBD des requêtes correspondant aux deux objets `Driver-update` précédents et la mise-à-jour des données relationnelles dans la base. Ces requêtes et objets sont examinés dans la prochaine section (§4.2.2.3). L'appel à la fonction `update-object` se termine en retournant la clé de l'objet mis à jour, soit `emp/7655`.

Après l'examen de l'objet `james`, la validation du `class_object_buffer` de la classe `Employé` se poursuit. Cependant, lors du traitement du champ `responsable-de` de `james`, les objets qui se trouvaient encore dans ce `class_object_buffer` ont été validés et en ont donc été retirés. Ainsi, il est vide maintenant. L'exécution de `commit-class-objects` pour le `class_object_buffer` de la classe `Employé` se termine donc immédiatement en retournant la liste des clés des objets validés au cours de la même opération, soit `{emp/7655}`. La validation du `class_object_buffer` de la classe `Vendeur` est aussitôt enchaînée.

### 4.2.2.3 Les requêtes de mise-à-jour

`DRIVER` modifie dans la base un objet relationnel en adressant au SGBD une requête de mise-à-jour. Une telle requête est représentée par un objet de la classe `Driver-update`. Cet objet est progressivement construit par les méthodes `info-for-update` (cf. §4.2.2.2) et, une fois complet, permet de générer une requête SQL correspondante.
```sql
UPDATE tablename
    [FROM tablename]
    SET column = select_expr | NULL {, ... }
    [WHERE search_condition]
```

<caption>Figure 4.26 : Syntaxe d'une requête SQL de mise-à-jour de données</caption>

La syntaxe générale de la requête SQL de mise-à-jour est donnée figure 4.26. Disons, en quelques mots, que la requête est introduite par le mot-clé **UPDATE** et qu'elle s'adresse à une seule table. Les colonnes modifiées et leurs nouvelles valeurs sont précisées derrière le mot clé **SET** et les n-uplets concernés sont ceux qui vérifient les contraintes exprimées dans la **search_cond**.

La classe **Driver-update** (listing 21) présente, dans sa structure, les principales articulations d'une requête SQL de mise-à-jour de données. Nous verrons que les requêtes SQL d'insertion de données comportent ces mêmes articulations, moins la partie **where** (cf. §4.2.3.7). De ce fait, **Driver-update** est définie comme sous-classe de **Driver-insert** qui est la classe des requêtes d'insertion.

**Driver-insert** comprend quatre champs. Le premier reçoit la table logique à laquelle s'applique la requête. **null-attributes** contient l'ensemble des attributs auxquels on affecte la valeur **NULL**, et **attributes** l'ensemble des attributs auxquels on affecte une valeur différente de **NULL**. La liste de ces valeurs est précisée dans le champ **values**. Le champ **where**, propre à la classe **Driver-update**, contient pour sa part un ensemble de restrictions qui détermine l'ensemble des n-uplets auxquels s'appliquent les modifications.

Les classes **Driver-insert** et **Driver-update** répondent au message **generate-sql**. Leurs méthodes construisent et renvoient sous forme d'une chaîne de caractères la requête SQL correspondant à un objet.

<caption>Listing 21. Les classes de représentation des requêtes d'écriture</caption>

```python
(driver-defclass Driver-insert ()
    (table      object      Driver-table)
    (null-attributes objectset Driver-attribute)
    (attributes  ordobjset   Driver-attribute)
    (values     ordatomset  )))

(driver-defclass Driver-update Driver-insert
    (where objectset Driver-sql-op))
```