Documentation PostgreSQL 14.22 » Programmation serveur » Déclencheurs (triggers) » Aperçu du comportement des déclencheurs

Précédent | Niveau supérieur | Suivant
Chapitre 39. Déclencheurs (triggers) | Sommaire | 39.2. Visibilité des modifications des données

## 39.1. Aperçu du comportement des déclencheurs

Un déclencheur spécifie que la base de données doit exécuter automatiquement une fonction donnée chaque fois qu'un certain type d'opération est exécuté. Les fonctions déclencheur peuvent être attachées à une table (partitionnée ou non), une vue ou une table distante.

Sur des tables et tables distantes, les triggers peuvent être définies pour s'exécuter avant ou après une commande `INSERT`, `UPDATE` ou `DELETE`, soit une fois par ligne modifiée, soit une fois par expression SQL. Les triggers `UPDATE` peuvent en plus être configurées pour n'être déclenchés que si certaines colonnes sont mentionnées dans la clause `SET` de l'instruction `UPDATE`. Les triggers peuvent aussi se déclencher pour des instructions `TRUNCATE`. Si un événement d'un trigger intervient, la fonction du trigger est appelée au moment approprié pour gérer l'événement.

Des triggers peuvent être définies sur des vues pour exécuter des opérations à la place des commandes `INSERT`, `UPDATE` ou `DELETE`. Les triggers `INSTEAD OF` sont déclenchés une fois par ligne devant être modifiée dans la vue. C'est de la responsabilité de la fonction trigger de réaliser les modifications nécessaires pour que les tables de base sous-jacentes d'une vue et, si approprié, de renvoyer la ligne modifiée comme elle apparaîtra dans la vue. Les triggers sur les vues peuvent aussi être définis pour s'exécuter une fois par requête SQL statement, avant ou après des opérations `INSERT`, `UPDATE` ou `DELETE`. Néanmoins, de tels triggers sont déclenchés seulement s'il existe aussi un trigger `INSTEAD OF` sur la vue. Dans le cas contraire, toute requête ciblant la vue doit être réécrite en une requête affectant sa (ou ses) table(s) de base. Les triggers déclenchés seront ceux de(s) table(s) de base.

La fonction déclencheur doit être définie avant que le déclencheur lui-même puisse être créé. La fonction déclencheur doit être déclarée comme une fonction ne prenant aucun argument et retournant un type `trigger` (la fonction déclencheur reçoit ses entrées via une structure `TriggerData` passée spécifiquement, et non pas sous la forme d'arguments ordinaires de fonctions).

Une fois qu'une fonction déclencheur est créée, le déclencheur (trigger) est créé avec CREATE TRIGGER. La même fonction déclencheur est utilisable par plusieurs déclencheurs.

PostgreSQL offre des déclencheurs par ligne et par instruction. Avec un déclencheur mode ligne, la fonction du déclencheur est appelée une fois pour chaque ligne affectée par l'instruction qui a lancé le déclencheur. Au contraire, un déclencheur mode instruction n'est appelé qu'une seule fois lorsqu'une instruction appropriée est exécutée, quelque soit le nombre de lignes affectées par cette instruction. En particulier, une instruction n'affectant aucune ligne résultera toujours en l'exécution de tout déclencheur mode instruction applicable. Ces deux types sont quelque fois appelés respectivement des déclencheurs niveau ligne et des déclencheurs niveau instruction. Les triggers sur `TRUNCATE` peuvent seulement être définis au niveau instruction, et non pas au niveau ligne.

Les triggers sont aussi classifiées suivant qu'ils se déclenchent avant (before), après (after) ou à la place (instead of) de l'opération. Ils sont référencés respectivement comme des triggers `BEFORE`, `AFTER` et `INSTEAD OF`. Les triggers `BEFORE` au niveau requête se déclenchent avant que la requête ne commence quoi que ce soit alors que les triggers `AFTER` au niveau requête se déclenchent tout à la fin de la requête. Ces types de triggers peuvent être définis sur les tables, vues et tables externes. Les triggers `BEFORE` au niveau ligne se déclenchent immédiatement avant l'opération sur une ligne particulière alors que les triggers `AFTER` au niveau ligne se déclenchent à la fin de la requête (mais avant les triggers `AFTER` au niveau requête). Ces types de triggers peuvent seulement être définis sur les tables et sur les tables distantes, et non pas sur les vues. Les triggers `INSTEAD OF` peuvent seulement être définis sur des vues, et seulement au niveau ligne. Ils se déclenchent immédiatement pour chaque ligne de la vue identifiée comme nécessitant une action.

L'exécution d'un trigger `AFTER` peut être reporté à la fin de la transaction, plutôt qu'à la fin de la requête, s'il a été défini comme un trigger de contrainte. Dans tous les cas, un trigger est exécuté comme faisant partie de la même transaction que la requête qui l'a exécuté, donc si soit la requête soit le trigger renvoie une erreur, l'effet sera une annulation par ROLLBACK.

Une instruction qui cible une table parent dans un héritage ou une hiérarchie de partitionnement ne cause pas le déclenchement des tiggers au niveau requête des tables filles affectées. Seuls les triggers au niveau requête de la table parent sont déclenchés. Néanmoins, les triggers niveau ligne de toute table fille affecté seront déclenchés.

Si une commande `INSERT` contient une clause `ON CONFLICT DO UPDATE`, il est possible que les effets des déclencheurs niveau ligne `BEFORE` `INSERT` et `BEFORE` `UPDATE` puissent être tous les deux appliqués de telle sorte que leurs effets soient visibles dans la version finale de la ligne mise à jour, si une colonne `EXCLUDED` est référencée. Il n'est néanmoins pas nécessaire qu'il soit fait référence à une colonne `EXCLUDED` pour que les deux types de déclencheurs BEFORE s'exécutent tout de même. La possibilité d'avoir des résultats surprenants devrait être prise en compte quand il existe des déclencheurs niveau ligne `BEFORE` `INSERT` et `BEFORE` `UPDATE` qui tous les deux modifient la ligne sur le point d'être insérée ou mise à jour (ceci peut être problématique si les modifications sont plus ou moins équivalentes et si elles ne sont pas idempotente). Notez que les déclencheurs `UPDATE` niveau instruction sont exécutés lorsque la clause `ON CONFLICT DO UPDATE` est spécifiée, quand bien même aucune ligne ne serait affectée par la commande `UPDATE` (et même si la commande `UPDATE` n'est pas exécutée). Une commande `INSERT` avec une clause `ON CONFLICT DO UPDATE` exécutera d'abord les déclencheurs niveau instruction `BEFORE` `INSERT`, puis les déclencheurs niveau instruction `BEFORE` `UPDATE`, suivis par les déclencheurs niveau instruction `AFTER` `UPDATE`, puis finalement les déclencheurs niveau instruction `AFTER` `INSERT`.

Si un `UPDATE` sur une table partitionnée implique le déplacement d'une ligne vers une autre partition, il sera réalisé comme un `DELETE` de la partition originale, suivi d'un `INSERT` dans la nouvelle partition. Dans ce cas, les triggers `BEFORE` `UPDATE` niveau ligne et tous les triggers `BEFORE` `DELETE` niveau ligne sont déclenchés sur la partition originale. Puis tous les triggers `BEFORE` `INSERT` niveau ligne sont déclenchés sur la partition destination. La possibilité de résultats surprenants doit être considéré quand tous les triggers affectent la ligne déplacée. En ce qui concerne les triggers `AFTER ROW`, les triggers `AFTER` `DELETE` et `AFTER` `INSERT` sont appliqués mais les triggers `AFTER` `UPDATE` ne le sont pas car `UPDATE` a été convertis en un `DELETE` et un `INSERT`. Quant aux triggers niveau instruction, aucun des triggers `DELETE` et `INSERT` ne sont déclenchés, y compris en cas de déplacement de lignes. Seuls les triggers `UPDATE` définis sur la table cible utilisée dans une instruction `UPDATE` seront déclenchés.

Les fonctions déclencheurs appelées par des déclencheurs niveau instruction devraient toujours renvoyer `NULL`. Les fonctions déclencheurs appelées par des déclencheurs niveau ligne peuvent renvoyer une ligne de la table (une valeur de type `HeapTuple`) vers l'exécuteur appelant, s'ils le veulent. Un déclencheur niveau ligne exécuté avant une opération a les choix suivants :

- Il peut retourner un pointeur `NULL` pour sauter l'opération pour la ligne courante. Ceci donne comme instruction à l'exécuteur de ne pas exécuter l'opération niveau ligne qui a lancé le déclencheur (l'insertion, la modification ou la suppression d'une ligne particulière de la table).

- Pour les déclencheurs `INSERT` et `UPDATE` de niveau ligne uniquement, la valeur de retour devient la ligne qui sera insérée ou remplacera la ligne en cours de mise à jour. Ceci permet à la fonction déclencheur de modifier la ligne en cours d'insertion ou de mise à jour.

Un déclencheur `BEFORE` niveau ligne qui ne serait pas conçu pour avoir l'un de ces comportements doit prendre garde à retourner la même ligne que celle qui lui a été passée comme nouvelle ligne (c'est-à-dire : pour des déclencheurs `INSERT` et `UPDATE` : la nouvelle (`NEW`) ligne, et pour les déclencheurs `DELETE`) : l'ancienne (`OLD`) ligne .

Un trigger `INSTEAD OF` niveau ligne devrait renvoyer soit `NULL` pour indiquer qu'il n'a pas modifié de données des tables de base sous-jacentes de la vue, soit la ligne de la vue qui lui a été passé (la ligne `NEW` pour les opérations `INSERT` et `UPDATE`, ou la ligne `OLD` pour l'opération `DELETE`). Une valeur de retour différent de NULL est utilisée comme signal indiquant que le trigger a réalisé les modifications de données nécessaires dans la vue. Ceci causera l'incrémentation du nombre de lignes affectées par la commande. Pour les opérations `INSERT` et `UPDATE` seulement, le trigger peut modifier la ligne `NEW` avant de la renvoyer. Ceci modifiera les données renvoyées par `INSERT RETURNING` ou `UPDATE RETURNING`, et est utile quand la vue n'affichera pas exactement les données fournies.

La valeur de retour est ignorée pour les déclencheurs niveau ligne lancés après une opération. Ils peuvent donc renvoyer la valeur `NULL`.

Certaines considérations s'appliquent pour les colonnes générées. Les colonnes générées sont calculées après les triggers `BEFORE` et avant les triggers `AFTER`. De ce fait, la valeur générée peut être inspectée dans les triggers `AFTER`. Dans les triggers `BEFORE`, la ligne `OLD` contient l'ancienne valeur générée, comme on pourrait s'y attendre, mais la ligne `NEW` ne contient pas encore la nouvelle valeur générée et ne doit pas être accédée. Dans l'interface en langage C, le contenu de la colonne est non défini à ce moment ; un langage de programmation de plus haut niveau doit empêcher l'accès à une colonne générée dans la ligne `NEW` pour un trigger `BEFORE`. Les modifications de la valeur d'une colonne générée dans un trigger `BEFORE` sont ignorées et seront écrasées.

Si plus d'un déclencheur est défini pour le même événement sur la même relation, les déclencheurs seront lancés dans l'ordre alphabétique de leur nom. Dans le cas de déclencheurs `BEFORE` et `INSTEAD OF`, la ligne renvoyée par chaque déclencheur, qui a éventuellement été modifiée, devient l'argument du prochain déclencheur. Si un des déclencheurs `BEFORE` ou `INSTEAD OF` renvoie un pointeur `NULL`, l'opération est abandonnée pour cette ligne et les déclencheurs suivants ne sont pas lancés (pour cette ligne).

Une définition de trigger peut aussi spécifier une condition booléenne `WHEN` qui sera testée pour savoir si le trigger doit bien être déclenché. Dans les triggers de niveau ligne, la condition `WHEN` peut examiner l'ancienne et la nouvelle valeur des colonnes de la ligne. (les triggers de niveau instruction peuvent aussi avoir des conditions `WHEN` mais cette fonctionnalité est moins intéressante pour elles). Dans un trigger avant, la condition `WHEN` est évaluée juste avant l'exécution de la fonction, donc l'utilisation de `WHEN` n'est pas réellement différente du test de la même condition au début de la fonction trigger. Néanmoins, dans un tigger `AFTER`, la condition `WHEN` est évaluée juste avant la mise à jour de la ligne et détermine si un événement va déclencher le trigger à la fin de l'instruction. Donc, quand la condition `WHEN` d'un trigger `AFTER` ne renvoie pas true, il n'est pas nécessaire de mettre en queue un événement ou de récupérer de nouveau la ligne à la fin de l'instruction. Ceci permet une amélioration conséquente des performances pour les instructions qui modifient un grand nombre de lignes si le trigger a seulement besoin d'être exécuté que sur quelques lignes. Les triggers `INSTEAD OF` n'acceptent pas les conditions `WHEN`.

Les déclencheurs `BEFORE` en mode ligne sont typiquement utilisés pour vérifier ou modifier les données qui seront insérées ou mises à jour. Par exemple, un déclencheur `BEFORE` pourrait être utilisé pour insérer l'heure actuelle dans une colonne de type `timestamp` ou pour vérifier que deux éléments d'une ligne sont cohérents. Les déclencheurs `AFTER` en mode ligne sont pour la plupart utilisés pour propager des mises à jour vers d'autres tables ou pour réaliser des tests de cohérence avec d'autres tables. La raison de cette division du travail est qu'un déclencheur `AFTER` peut être certain qu'il voit la valeur finale de la ligne alors qu'un déclencheur `BEFORE` ne l'est pas ; il pourrait exister d'autres déclencheurs `BEFORE` qui seront exécutés après lui. Si vous n'avez aucune raison spéciale pour le moment du déclenchement, le cas `BEFORE` est plus efficace car l'information sur l'opération n'a pas besoin d'être sauvegardée jusqu'à la fin du traitement.

Si une fonction déclencheur exécute des commandes SQL, alors ces commandes peuvent lancer à leur tour des déclencheurs. On appelle ceci un déclencheur en cascade. Il n'y a pas de limitation directe du nombre de niveaux de cascade. Il est possible que les cascades causent un appel récursif du même déclencheur ; par exemple, un déclencheur `INSERT` pourrait exécuter une commande qui insère une ligne supplémentaire dans la même table, entraînant un nouveau lancement du déclencheur `INSERT`. Il est de la responsabilité du programmeur d'éviter les récursions infinies dans de tels scénarios.

Quand un déclencheur est défini, des arguments peuvent être spécifiés pour lui. L'objectif de l'inclusion d'arguments dans la définition du déclencheur est de permettre à différents déclencheurs ayant des exigences similaires d'appeler la même fonction. Par exemple, il pourrait y avoir une fonction déclencheur généralisée qui prend comme arguments deux noms de colonnes et place l'utilisateur courant dans l'une et un horodatage dans l'autre. Correctement écrit, cette fonction déclencheur serait indépendante de la table particulière sur laquelle il se déclenche. Ainsi, la même fonction pourrait être utilisée pour des événements `INSERT` sur n'importe quelle table ayant des colonnes adéquates, pour automatiquement suivre les créations d'enregistrements dans une table de transactions par exemple. Elle pourrait aussi être utilisée pour suivre les dernières mises à jours si elle est définie comme un déclencheur `UPDATE`.

Chaque langage de programmation supportant les déclencheurs a sa propre méthode pour rendre les données en entrée disponible à la fonction du déclencheur. Cette donnée en entrée inclut le type d'événement du déclencheur (c'est-à-dire `INSERT` ou `UPDATE`) ainsi que tous les arguments listés dans `CREATE TRIGGER`. Pour un déclencheur niveau ligne, la donnée en entrée inclut aussi la ligne `NEW` pour les déclencheurs `INSERT` et `UPDATE` et/ou la ligne `OLD` pour les déclencheurs `UPDATE` et `DELETE`.

Par défaut, les triggers niveau instruction n'ont aucun moyen d'examiner le ou les lignes individuelles modifiées par la requête. Mais un trigger `AFTER STATEMENT` peut demander que des tables de transition soient créées pour rendre disponible les ensembles de lignes affectées au trigger. `AFTER ROW` peut aussi demander les tables de transactions, pour accéder au changement global dans la table, ainsi qu'au changement de lignes individuelles pour lesquels ils ont été déclenchés. La méthode d'examen des tables de transition dépend là-aussi du langage de programmation utilisé mais l'approche typique est de transformer les tables de transition en tables temporaires en lecture seule pouvant être accédées par des commandes SQL lancées par la fonction trigger.