Tous les appels de fonctions écrites dans un langage autre que celui de l'interface « version 1 » pour les langages compilés (ce qui inclut les fonctions dans les langages procéduraux utilisateur, les fonctions SQL), passent par une fonction spécifique au langage du gestionnaire d'appels. Le gestionnaire d'appels exécute la fonction de manière appropriée, par exemple en interprétant le code source fourni. Ce chapitre décrit l'écriture du gestionnaire d'appels d'un nouveau langage procédural.
Le gestionnaire d'appel d'un langage procédural est une fonction
« normale » qui doit être écrite dans un langage compilé tel que
le C, en utilisant l'interface version-1, et enregistrée sous
PostgreSQL comme une fonction sans argument
et retournant le type language_handler
. Ce pseudo-type spécial
identifie la fonction comme gestionnaire d'appel et empêche son appel
à partir des commandes SQL.
Pour plus de détails sur les conventions d'appels et le chargement
dynamique en langage C, voir Section 37.9.
L'appel du gestionnaire d'appels est identique à celui de toute
autre fonction : il reçoit un pointeur de structure
FunctionCallInfoData
qui contient les
valeurs des arguments et d'autres informations de la fonction appelée.
Il retourne un résultat Datum
(et, initialise
le champ isnull
de la structure
FunctionCallInfoData
si un résultat SQL NULL doit
être retourné). La différence entre un gestionnaire d'appels et une
fonction ordinaire se situe au niveau du champ
flinfo->fn_oid
de la structure
FunctionCallInfoData
. Dans le cas du gestionnaire
d'appels, il contiendra l'OID de la fonction à appeler, et non pas celui du
gestionnaire d'appels lui-même.
Le gestionnaire d'appels utilise ce champ pour déterminer la
fonction à exécuter. De plus, la liste d'arguments passée a été dressée
à partir de la déclaration de la fonction cible, et non pas en
fonction du gestionnaire d'appels.
C'est le gestionnaire d'appels qui récupère l'entrée de la
fonction dans la table système pg_proc
et
analyse les types des arguments et de la valeur de retour de la fonction
appelée. La clause AS
de la commande
CREATE FUNCTION
se situe dans la
colonne prosrc
de
pg_proc
. Il s'agit généralement du texte source du
langage procédural lui-même (comme pour PL/Tcl) mais, en théorie, cela
peut être un chemin vers un fichier ou tout ce qui indique
au gestionnaire d'appels les détails des actions à effectuer.
Souvent, la même fonction est appelée plusieurs fois dans la même
instruction SQL.
L'utilisation du champ flinfo->fn_extra
évite au gestionnaire d'appels de répéter la recherche des informations
concernant la fonction appelée. Ce champ, initialement
NULL
, peut être configuré par le gestionnaire d'appels pour
pointer sur l'information concernant la fonction appelée. Lors des appels
suivants, si flinfo->fn_extra
est différent
de NULL
, alors il peut être utilisé et l'étape de
recherche d'information évitée. Le gestionnaire d'appels doit
s'assurer que flinfo->fn_extra
pointe sur une
zone mémoire qui restera allouée au moins jusqu'à la fin de la requête en
cours, car une structure de données FmgrInfo
peut
être conservée aussi longtemps. Cela peut-être obtenu par l'allocation
des données supplémentaires dans le contexte mémoire spécifié par
flinfo->fn_mcxt
; de telles données
ont la même espérance de vie que FmgrInfo
.
Le gestionnaire peut également choisir d'utiliser un contexte mémoire de
plus longue espérance de vie de façon à
mettre en cache sur plusieurs
requêtes les informations concernant les définitions des fonctions.
Lorsqu'une fonction en langage procédural est appelée via un déclencheur,
aucun argument ne lui est passé de façon traditionnelle mais le champ
context
de
FunctionCallInfoData
pointe sur une structure
TriggerData
. Il n'est pas
NULL
comme c'est le cas dans les appels de fonctions standard.
Un gestionnaire de langage doit fournir les mécanismes pour que les
fonctions de langages procéduraux obtiennent les informations du
déclencheur.
Voici un modèle de gestionnaire de langage procédural écrit en C :
#include "postgres.h" #include "executor/spi.h" #include "commands/trigger.h" #include "fmgr.h" #include "access/heapam.h" #include "utils/syscache.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif PG_FUNCTION_INFO_V1(plsample_call_handler); Datum plsample_call_handler(PG_FUNCTION_ARGS) { Datum retval; if (CALLED_AS_TRIGGER(fcinfo)) { /* * Appelé comme procédure de déclencheur */ TriggerData *trigdata = (TriggerData *) fcinfo->context; retval = ... } else { /* * Appelé en tant que fonction */ retval = ... } return retval; }
Il suffit de remplacer les points de suspension par quelques milliers de lignes de codes pour compléter ce modèle.
Lorsque la fonction du gestionnaire est compilée dans un module chargeable (voir Section 37.9.5), les commandes suivantes enregistrent le langage procédural défini dans l'exemple :
CREATE FUNCTION plsample_call_handler() RETURNS language_handler
AS 'nomfichier
'
LANGUAGE C;
CREATE LANGUAGE plsample
HANDLER plsample_call_handler;
Bien que fournir un gestionnaire d'appels est suffisant pour créer un langage de procédures minimal, il existe deux autres fonctions qui peuvent être fournies pour faciliter l'utilisation du langage. Ce sont les fonctions de validation (validator) et de traitement en ligne (inline handler). Une fonction de validation peut être fournie pour activer une vérification spécifique au langage lors du CREATE FUNCTION. Une fonction de traitement en ligne sera utilisé pour supporter les blocs de code anonymes exécutés via la commande DO.
Si une fonction de validation est fournie par un langage de procédures,
elle doit être déclarée comme une fonction prenant un seul paramètre, de
type oid
. Le résultat de la validation est ignoré, donc elle
peut renvoyer le type void
. La fonction de validation sera
appelée à la fin de la commande CREATE FUNCTION
qui a
créé ou mis à jour une fonction écrite dans ce langage. L'OID passé en
argument est l'OID de la fonction, disponible dans le catalogue
pg_proc
. La fonction de validation doit récupérer
cette ligne de la façon habituelle et réaliser les vérifications
appropriées. Tout d'abord, elle appelle
CheckFunctionValidatorAccess()
pour diagnostiquer les
appels explicites au validateur que l'utilisateur ne peut pas réaliser
via CREATE FUNCTION
.
Les vérifications typiques incluent la vérification du
support des types en arguments et en sortie, ainsi que la vérification
syntaxique du corps de la requête pour ce langage. Si la fonction de
validation est satisfait par la fonction, elle quitte sans erreur. Si, par
contre, elle trouve une erreur, elle doit rapporter cette erreur au travers
du mécanisme ereport()
standard. Renvoyer une erreur
forcera une annulation de la transaction et empêchera du même coup
l'enregistrement de la fonction dont la définition est erronée.
Les fonctions de validation devraient typiquement accepter le paramètre
check_function_bodies : s'il est désactivé,
alors tout vérification coûteuse ou spécifique au contexte devrait être
abandonnée. Si le langage permet l'exécution de code à la compilation,
le validateur doit supprimer les vérifications qui impliquerait une
telle exécution. En particulier, ce paramètre est désactivé par
pg_dump, pour qu'il puisse charger le langage
de procédures sans avoir à s'inquiéter des effets de bord et des dépendances
possibles dans le
corps des procédures stockées avec d'autres objets de la base de données.
(À cause de cela, le gestionnaire d'appels doit éviter de supposer que la
fonction de validation a vérifié complètement la fonction. Le but d'avoir
une fonction de validation n'est pas d'éviter au gestionnaire d'appels de
faire des vérifications, mais plutôt de notifier immédiatement à
l'utilisateur si des erreurs évidentes apparaissent dans la commande
CREATE FUNCTION
.)
Bien que le choix de ce qui est à vérifier est laissé à la discrétion de la
fonction de validation, il faut noter que le code de CREATE
FUNCTION
exécute seulement les clauses SET
attachées à la fonction quand le paramètre
check_function_bodies
est activé. Du coup, les vérifications
dont les résultats pourraient être affectés par les paramètres en question
doivent être ignorées quand check_function_bodies
est
désactivé pour éviter de échecs erronés lors du chargement d'une sauvegarde.
Si une fonction de traitement en ligne est fournie au langage de
procédures, elle doit être déclarée comme une fonction acceptant un seul
paramètre de type internal
. Le résultat de la fonction de
traitement en ligne est ignoré, donc elle peut renvoyer le type
void
. Elle sera appelée quand une instruction
DO
est exécutée pour ce langage. Le paramètre qui lui
est fourni est un pointeur vers une structure
InlineCodeBlock
, structure contenant des
informations sur les paramètres de l'instruction DO
, en
particulier le texte du bloc de code anonyme à exécuter. La fonction doit
exécuter ce code.
It est recommandé de placer toutes les déclarations de fonctions ainsi que
la commande CREATE LANGUAGE
dans une
extension pour qu'une simple commande
CREATE EXTENSION
suffise à installer le langage. Voir
Section 37.15 pour plus d'informations sur
l'écriture d'extensions.
Les langages procéduraux inclus dans la distribution standard sont de
bons points de départ à l'écriture de son propre gestionnaire
de langage. Les sources se trouvent dans le répertoire src/pl
.
La page de référence de CREATE LANGUAGE contient aussi
certains détails utiles.