Documentation PostgreSQL 8.0.2 | ||||
---|---|---|---|---|
Précédent | Arrière rapide | Chapitre 33. Système de règles | Avance rapide | Suivant |
Les règles définies sur INSERT, UPDATE et DELETE sont significativement différentes des règles de vue décrites dans la section précédente. Tout d'abord, leur commande CREATE RULE permet plus de choses :
Elles peuvent n'avoir aucune action.
Elles peuvent avoir plusieurs actions.
Elles peuvent être de type INSTEAD ou ou ALSO (valeur par défaut).
Les pseudo relations NEW et OLD deviennent utiles.
Elles peuvent avoir des qualifications de règles.
Ensuite, elles ne modifient pas l'arbre de requête en place. À la place, elles créent de nouveaux arbres de requêtes et peuvent abandonner l'original.
Gardez la syntaxe
CREATE RULE rule_name AS ON event TO object [WHERE rule_qualification] DO [ALSO|INSTEAD] [action | (actions) | NOTHING];
en tête. Dans la suite, règles de mise à jour signifie les règles qui sont définies sur INSERT, UPDATE ou DELETE.
Les règles de mise à jour sont appliquées par le système de règles lorsque la relation résultante et le type de commande d'un arbre de requête sont égaux pour l'objet et l'événement donné dans la commande CREATE RULE. Pour les règles de mise à jour, le système de règles crée une liste d'arbres de requêtes. Initialement, la liste d'arbres de requêtes est vide. Il peut y avoir aucune (mot clé NOTHING), une ou plusieurs actions. Pour simplifier, nous verrons une règle avec une action. Cette règle peut avoir une qualification et peut être de type INSTEAD ou ALSO (valeur par défaut).
Qu'est-ce qu'une qualification de règle ? C'est une restriction indiquant le moment où doivent être réalisés les actions de la règle. Cette qualification peut seulement référencer les pseudo relations NEW et/ou OLD, qui représentent basiquement la relation qui a été donné comme objet (mais avec une signification spéciale).
Donc, nous avons quatre cas qui produisent les arbres de requêtes suivant pour une règle à une seule action.
l'arbre de requête à partir de l'action de la règle avec l'ajout de la qualification de l'arbre de requête
l'arbre de requête à partir de l'action de la règle avec l'ajout de la qualification de l'arbre de requête original
l'arbre de requête à partir de l'action de la règle avec l'ajout de la qualification de la règle et de la qualification de l'arbre de requête original
l'arbre de requête à partir de l'action de la règle avec la qualification de la requête et la qualification de l'arbre de requête original ; et l'ajout de l'arbre de requête original avec la qualification inverse de la règle
Enfin, si la règle est ALSO, l'arbre de requête original est ajouté à la liste. Comme seules les règles qualifiées INSTEAD ont déjà ajouté l'arbre de requête original, nous finissons avec un ou deux arbres de requête en sortie pour une règle avec une action.
Pour les règles ON INSERT, la requête originale (si elle n'est pas supprimée par INSTEAD) est réalisée avant toute action ajoutée par les règles. Ceci permet aux actions de voir les lignes insérées. Mais pour les règles ON UPDATE et ON DELETE, la requête originale est réalisée après les actions ajoutées par les règles. Ceci nous assure que les actions pourront voir les lignes à mettre à jour ou à supprimer ; sinon, les actions pourraient ne rien faire parce qu'elles ne trouvent aucune ligne correspondant à leurs qualifications.
Les arbres de requêtes générés à partir des actions de règles sont envoyés de nouveau dans le système de réécriture et peut-être que d'autres règles seront appliquées résultant en plus ou moins d'arbres de requêtes. Donc, les actions d'une règle doivent avoir soit un type de commande différent soit une relation résultante différente de celle où la règle elle-même est active, sinon ce processus récursif se terminera dans une boucle infinie. (L'expansion récursive d'une règle sera détectée et rapportée comme une erreur.)
Les arbres de requête trouvés dans les actions du catalogue système pg_rewrite sont seulement des modèles. Comme ils peuvent référencer les entrées de la table d'échelle pour NEW et OLD, quelques substitutions ont dû être faites avant qu'elles ne puissent être utilisées. Pour toute référence de NEW, une entrée correspondante est recherchée dans la liste cible de la requête originale. Si elle est trouvée, cette expression de l'entrée remplace la référence. Sinon, NEW signifie la même chose que OLD (pour un UPDATE) ou est remplacé par une valeur null (pour un INSERT). Toute référence à OLD est remplacée par une référence à l'entrée de la table d'échelle qui est la relation résultante.
Après que le système a terminé d'appliquer des règles de mise à jour, il applique les règles de vues pour le(s) arbre(s) de requête produit(s). Les vues ne peuvent pas insérer de nouvelles actions de mise à jour, donc il n'est pas nécessaire d'appliquer les règles de mise à jour à la sortie d'une réécriture de vue.
Disons que nous voulons tracer les modifications dans la colonne sl_avail de la relation shoelace_data. Donc, nous allons configurer une table de traces et une règle qui va écrire une entrée lorsqu'un UPDATE est lancé sur shoelace_data.
CREATE TABLE shoelace_log ( sl_name text, -- modification de shoelace sl_avail integer, -- nouvelle valeur disponible log_who text, -- qui l'a modifié log_when timestamp -- quand ); CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data WHERE NEW.sl_avail <> OLD.sl_avail DO INSERT INTO shoelace_log VALUES ( NEW.sl_name, NEW.sl_avail, current_user, current_timestamp );
Maintenant, quelqu'un fait :
UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';
et nous regardons la table des traces :
SELECT * FROM shoelace_log; sl_name | sl_avail | log_who | log_when ---------+----------+---------+---------------------------------- sl7 | 6 | Al | Tue Oct 20 16:14:45 1998 MET DST (1 row)
C'est ce à quoi nous nous attendions. Voici ce qui s'est passé en tâche de fond. L'analyseur a créé l'arbre de requête
UPDATE shoelace_data SET sl_avail = 6 FROM shoelace_data shoelace_data WHERE shoelace_data.sl_name = 'sl7';
Il existe une règle log_shoelace qui est ON UPDATE avec l'expression de qualification de la règle
NEW.sl_avail <> OLD.sl_avail
et l'action
INSERT INTO shoelace_log VALUES ( *NEW*.sl_name, *NEW*.sl_avail, current_user, current_timestamp ) FROM shoelace_data *NEW*, shoelace_data *OLD*;
(Ceci semble un peu étrange car vous ne pouvez pas normalement écrire INSERT ... VALUES ... FROM. Ici, la clause FROM indique seulement qu'il existe des entrées de la table d'échelle dans l'arbre de requête pour *NEW* et *OLD*. Elles sont nécessaires pour qu'elles puissent être référencées par des variables dans l'arbre de requête de la commande INSERT.)
La règle est une règle qualifiée ALSO de façon à ce que le système de règles doit renvoyer deux arbres de requêtes : l'action de la règle modifiée et l'arbre de requête original. Dans la première étape, la table d'échelle de la requête original est incorporée dans l'arbre de requête d'action de la règle. Ceci a pour résultat :
INSERT INTO shoelace_log VALUES ( *NEW*.sl_name, *NEW*.sl_avail, current_user, current_timestamp ) FROM shoelace_data *NEW*, shoelace_data *OLD*, shoelace_data shoelace_data;
Pour la deuxième étape, la qualification de la règle lui est ajoutée, donc l'ensemble de résultat est restreint aux lignes où sl_avail a changé :
INSERT INTO shoelace_log VALUES ( *NEW*.sl_name, *NEW*.sl_avail, current_user, current_timestamp ) FROM shoelace_data *NEW*, shoelace_data *OLD*, shoelace_data shoelace_data WHERE *NEW*.sl_avail <> *OLD*.sl_avail;
(Ceci semble encore plus étrange car INSERT ... VALUES n'a pas non plus une clause WHERE mais le planificateur et l'exécuteur n'auront pas de difficultés avec ça. Ils ont besoin de supporter cette même fonctionnalité pour INSERT ... SELECT.)
À l'étape 3, la qualification de l'arbre de requête original est ajoutée, restreignant encore plus l'ensemble de résultats pour les seules lignes qui auront été modifiées par la requête originale :
INSERT INTO shoelace_log VALUES ( *NEW*.sl_name, *NEW*.sl_avail, current_user, current_timestamp ) FROM shoelace_data *NEW*, shoelace_data *OLD*, shoelace_data shoelace_data WHERE *NEW*.sl_avail <> *OLD*.sl_avail AND shoelace_data.sl_name = 'sl7';
La quatrième étape remplace les références à NEW par les entrées de la liste cible à partir de l'arbre de requête original ou par les références de la variable correspondante à partir de la relation résultat :
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data *NEW*, shoelace_data *OLD*, shoelace_data shoelace_data WHERE 6 <> *OLD*.sl_avail AND shoelace_data.sl_name = 'sl7';
L'étape 5 modifie les références OLD en référence de la relation résultat :
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data *NEW*, shoelace_data *OLD*, shoelace_data shoelace_data WHERE 6 <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7';
C'est tout. Comme la règle est de type ALSO, nous affichons aussi l'arbre de requêtes original. En bref, l'affichage à aprtir du système de règles est une liste de deux arbres de requêtes est une liste de deux arbres de requêtes correspondant à ces instructions :
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data WHERE 6 <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7'; UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';
Elles sont exécutées dans cet ordre et c'est exactement le but de la règle.
Les substitutions et les qualifications ajoutées nous assurent que, si la requête originale était,
UPDATE shoelace_data SET sl_color = 'green' WHERE sl_name = 'sl7';
aucune trace ne serait écrire. Dans ce cas, l'arbre de requête original ne contient pas une entrée dans la liste cible pour sl_avail, donc NEW.sl_avail sera remplacé par shoelace_data.sl_avail. Du coup, la commande supplémentaire générée par la règle est command generated by the rule is
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, shoelace_data.sl_avail, current_user, current_timestamp ) FROM shoelace_data WHERE shoelace_data.sl_avail <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7';
et la qualification ne sera jamais vraie.
Si la requête originale modifie plusieurs lignes, cela fonctionne aussi. Donc, si quelqu'un a lancé la commande
UPDATE shoelace_data SET sl_avail = 0 WHERE sl_color = 'black';
en fait, quatre lignes sont modifiées (sl1, sl2, sl3 et sl4). Mais sl3 a déjà sl_avail = 0. Dans ce cas, la qualification des arbres de requêtes originaux sont différents et cela produit un arbre de requête supplémentaire
INSERT INTO shoelace_log SELECT shoelace_data.sl_name, 0, current_user, current_timestamp FROM shoelace_data WHERE 0 <> shoelace_data.sl_avail AND shoelace_data.sl_color = 'black';
à générer par la règle. Cet arbre de requête aura sûrement insérer trois nouvelles lignes de traces. Et c'est tout à fait correct.
Ici, nous avons vu pourquoi il est important que l'arbre de requête original soit exécuté en premier. Si l'UPDATE a été exécutée avant, toutes les lignes pourraient aussi être intialisées à zéro, donc le INSERT tracé ne trouvera aucune ligne à 0 <> shoelace_data.sl_avail.
Une façon simple de protéger les relations d'une vue de la possibilité mentionnée que quelqu'un essaie de lancer des INSERT, UPDATE ou DELETE sur elles est de laisser s'abandonner ces arbres de requête. Donc, nous créons les règles
CREATE RULE shoe_ins_protect AS ON INSERT TO shoe DO INSTEAD NOTHING; CREATE RULE shoe_upd_protect AS ON UPDATE TO shoe DO INSTEAD NOTHING; CREATE RULE shoe_del_protect AS ON DELETE TO shoe DO INSTEAD NOTHING;
Maintenant, si quelqu'un essaie de faire une de ces opérations sur la vue shoe, le système de règles appliquera ces règles. Comme les règles n'ont pas d'action et sont de type INSTEAD, la liste résultant des arbres de requêtes sera vide et la requête entière deviendra vide car il ne reste rien à optimiser ou exécuter après que le système de règles en ait terminé avec elle.
Une façon plus sophistiquée d'utiliser le système de règles est de créer les règles qui réécrivent l'arbre de requête en un faisant la bonne opération sur les vraies tables. Pour réaliser cela sur la vue shoelace, nous créons les règles suivantes :
CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data VALUES ( NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit ); CREATE RULE shoelace_upd AS ON UPDATE TO shoelace DO INSTEAD UPDATE shoelace_data SET sl_name = NEW.sl_name, sl_avail = NEW.sl_avail, sl_color = NEW.sl_color, sl_len = NEW.sl_len, sl_unit = NEW.sl_unit WHERE sl_name = OLD.sl_name; CREATE RULE shoelace_del AS ON DELETE TO shoelace DO INSTEAD DELETE FROM shoelace_data WHERE sl_name = OLD.sl_name;
Maintenant, supposons que quelque fois, un paquet de lacets arrive au magasin avec une grosse liste. Mais vous ne voulez pas mettre à jour manuellement la vue shoelace à chaque fois. À la place, nous configurons deux petites tables une où vous pouvez insérer les éléments de la liste et une avec une astuce spéciale. Voici les commandes de création :
CREATE TABLE shoelace_arrive ( arr_name text, arr_quant integer ); CREATE TABLE shoelace_ok ( ok_name text, ok_quant integer ); CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok DO INSTEAD UPDATE shoelace SET sl_avail = sl_avail + NEW.ok_quant WHERE sl_name = NEW.ok_name;
Maintenant, vous pouvez remplir la table shoelace_arrive avec les données de la liste :
SELECT * FROM shoelace_arrive; arr_name | arr_quant ----------+----------- sl3 | 10 sl6 | 20 sl8 | 20 (3 rows)
Jetez un œil rapidement aux données actuelles :
SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl3 | 0 | black | 35 | inch | 88.9 sl4 | 8 | black | 40 | inch | 101.6 sl8 | 1 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 0 | brown | 0.9 | m | 90 (8 rows)
Maintenant, déplacez les lacets arrivés dans :
INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;
et vérifiez le résultat :
SELECT * FROM shoelace ORDER BY sl_name; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl4 | 8 | black | 40 | inch | 101.6 sl3 | 10 | black | 35 | inch | 88.9 sl8 | 21 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 20 | brown | 0.9 | m | 90 (8 rows) SELECT * FROM shoelace_log; sl_name | sl_avail | log_who| log_when ---------+----------+--------+---------------------------------- sl7 | 6 | Al | Tue Oct 20 19:14:45 1998 MET DST sl3 | 10 | Al | Tue Oct 20 19:25:16 1998 MET DST sl6 | 20 | Al | Tue Oct 20 19:25:16 1998 MET DST sl8 | 21 | Al | Tue Oct 20 19:25:16 1998 MET DST (4 rows)
C'est un long chemin du INSERT ... SELECT à ces résultats. Et la description de la transformation de l'arbre de requêtes sera la dernière dans ce chapitre. Tout d'abord, voici la sortie de l'analyseur
INSERT INTO shoelace_ok SELECT shoelace_arrive.arr_name, shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;
Maintenant, la première règle shoelace_ok_ins est appliquée et transforme ceci en
UPDATE shoelace SET sl_avail = shoelace.sl_avail + shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok *OLD*, shoelace_ok *NEW*, shoelace shoelace WHERE shoelace.sl_name = shoelace_arrive.arr_name;
et jette l'INSERT actuel sur shoelace_ok. La requête réécrite est passée de nouveau au système de règles et la seconde règle appliquée shoelace_upd produit
UPDATE shoelace_data SET sl_name = shoelace.sl_name, sl_avail = shoelace.sl_avail + shoelace_arrive.arr_quant, sl_color = shoelace.sl_color, sl_len = shoelace.sl_len, sl_unit = shoelace.sl_unit FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok *OLD*, shoelace_ok *NEW*, shoelace shoelace, shoelace *OLD*, shoelace *NEW*, shoelace_data shoelace_data WHERE shoelace.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = shoelace.sl_name;
De nouveau, il s'agit d'une règle INSTEAD et l'arbre de requête précédent est jeté. Notez que cette requête utilise toujours la vue shoelace. Mais le système de règles n'a pas fini cette étape, donc il continue et lui applique la règle _RETURN. Nous obtenons
UPDATE shoelace_data SET sl_name = s.sl_name, sl_avail = s.sl_avail + shoelace_arrive.arr_quant, sl_color = s.sl_color, sl_len = s.sl_len, sl_unit = s.sl_unit FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok *OLD*, shoelace_ok *NEW*, shoelace shoelace, shoelace *OLD*, shoelace *NEW*, shoelace_data shoelace_data, shoelace *OLD*, shoelace *NEW*, shoelace_data s, unit u WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name;
Enfin, la règle log_shoelace est appliquée, produisant l'arbre de requête supplémentaire
INSERT INTO shoelace_log SELECT s.sl_name, s.sl_avail + shoelace_arrive.arr_quant, current_user, current_timestamp FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok *OLD*, shoelace_ok *NEW*, shoelace shoelace, shoelace *OLD*, shoelace *NEW*, shoelace_data shoelace_data, shoelace *OLD*, shoelace *NEW*, shoelace_data s, unit u, shoelace_data *OLD*, shoelace_data *NEW* shoelace_log shoelace_log WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name AND (s.sl_avail + shoelace_arrive.arr_quant) <> s.sl_avail;
Une fois que le système de règles tombe en panne de règles et renvoie les arbres de requêtes générés.
Donc, nous finissons avec deux arbres de requêtes finaux qui sont équivalents aux instructions SQL
INSERT INTO shoelace_log SELECT s.sl_name, s.sl_avail + shoelace_arrive.arr_quant, current_user, current_timestamp FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data, shoelace_data s WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name AND s.sl_avail + shoelace_arrive.arr_quant <> s.sl_avail; UPDATE shoelace_data SET sl_avail = shoelace_data.sl_avail + shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data, shoelace_data s WHERE s.sl_name = shoelace_arrive.sl_name AND shoelace_data.sl_name = s.sl_name;
Le résultat est que la donnée provenant d'une relation insérée dans une autre, modifiée en mise à jour dans une troisième, modifiée en mise à jour dans une quatrième, cette dernière étant tracée dans une cinquième, se voit réduite à deux requêtes.
Il y a un petit détail assez horrible. En regardant les deux requêtes, nous nous apercevons que la relation shoelace_data apparait deux fois dans la table d'échelle où cela pourrait être réduit à une seule occurence. Le planificateur ne gère pas ceci et, du coup, le plan d'exécution de la sortie du système de règles pour INSERT sera
Nested Loop -> Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive -> Seq Scan on shoelace_data
alors qu'omettre la table d'échelle supplémentaire résulterait en un
Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive
qui produit exactement les mêmes entrées dans la table des traces. Du coup, le système de règles a causé un parcours supplémentaire dans la table shoelace_data qui n'est absolument pas nécessaire. Et le même parcours redondant est fait une fois de plus dans l'UPDATE. Mais ce fut réellement un travail difficile de rendre tout ceci possible.
Maintenant, nous faisons une démonstration finale du système de règles de PostgreSQL et de sa puissance. Disons que nous ajoutons quelques lacets avec des couleurs extraordinaires à votre base de données :
INSERT INTO shoelace VALUES ('sl9', 0, 'pink', 35.0, 'inch', 0.0); INSERT INTO shoelace VALUES ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
Nous voulons créer une vue vérifiant les entrées shoelace qui ne correspondent à aucune chaussure pour la couleur. Voici la vue
CREATE VIEW shoelace_mismatch AS SELECT * FROM shoelace WHERE NOT EXISTS (SELECT shoename FROM shoe WHERE slcolor = sl_color);
Sa sortie est
SELECT * FROM shoelace_mismatch; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ---------+----------+----------+--------+---------+----------- sl9 | 0 | pink | 35 | inch | 88.9 sl10 | 1000 | magenta | 40 | inch | 101.6
Maintenant, nous voulons le configurer pour que les lacets qui ne correspondent pas et qui ne sont pas en stock sont supprimés de la base de données. Pour rendre la chose plus difficile à PostgreSQL, nous ne les supprimerons pas directement. À la place, nous créons une vue supplémentaire
CREATE VIEW shoelace_can_delete AS SELECT * FROM shoelace_mismatch WHERE sl_avail = 0;
et le faisons de cette façon :
DELETE FROM shoelace WHERE EXISTS (SELECT * FROM shoelace_can_delete WHERE sl_name = shoelace.sl_name);
Voilà:
SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ---------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl4 | 8 | black | 40 | inch | 101.6 sl3 | 10 | black | 35 | inch | 88.9 sl8 | 21 | brown | 40 | inch | 101.6 sl10 | 1000 | magenta | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 20 | brown | 0.9 | m | 90 (9 rows)
Un DELETE sur une vue, avec une qualification de sous-requête qui utilise au total quatre vues imbriquées/jointes, où l'entre d'entre elles a une qualification de sous-requête contenant une vue et où les colonnes des vues calculées sont utilisées, est réécrite en un seul arbre de requête qui supprime les données demandées sur la vraie table.
Il existe probablement seulement quelques situations dans le vrai monde où une telle construction est nécessaire. Mais, vous vous sentez mieux quand cela fonctionne.
Précédent | Sommaire | Suivant |
Vues et système de règles | Niveau supérieur | Règles et droits |