Les fonctions définies par l'utilisateur peuvent être écrites en C (ou dans un langage pouvant être rendu compatible avec C, comme le C++). Ces fonctions sont compilées en objets dynamiques chargeables (encore appelés bibliothèques partagées) et sont chargées par le serveur à la demande. Cette caractéristique de chargement dynamique est ce qui distingue les fonctions en « langage C » des fonctions « internes » -- les véritables conventions de codage sont essentiellement les mêmes pour les deux (c'est pourquoi la bibliothèque standard de fonctions internes est une source abondante d'exemples de code pour les fonctions C définies par l'utilisateur).
Actuellement, seule une convention d'appel est utilisée pour les
fonctions C (« version 1 »). Le support pour cette convention
d'appel est indiqué en ajoutant un appel à la macro
PG_FUNCTION_INFO_V1()
pour la fonction, comme illustré
ci-dessous.
La première fois qu'une fonction définie par l'utilisateur dans un fichier
objet particulier chargeable est appelée dans une session, le chargeur
dynamique charge ce fichier objet en mémoire de telle sorte que la fonction
peut être appelée. La commande CREATE FUNCTION
pour une
fonction en C définie par l'utilisateur doit par conséquent spécifier deux
éléments d'information pour la fonction : le nom du fichier objet
chargeable et le nom en C (lien symbolique) de la fonction spécifique à
appeler à l'intérieur de ce fichier objet. Si le nom en C n'est pas
explicitement spécifié, il est supposé être le même que le nom de la
fonction SQL.
L'algorithme suivant, basé sur le nom donné dans la commande
CREATE FUNCTION
, est utilisé pour localiser le fichier
objet partagé :
Si le nom est un chemin absolu, le fichier est chargé.
Si le nom commence par la chaîne $libdir
, cette chaîne
est remplacée par le nom du répertoire de la bibliothèque du paquetage
PostgreSQL, qui est déterminé au moment de la
compilation.
Si le nom ne contient pas de partie répertoire, le fichier est recherché par le chemin spécifié dans la variable de configuration dynamic_library_path.
Dans les autres cas, (nom de fichier non trouvé dans le chemin ou ne contenant pas de partie répertoire non absolu), le chargeur dynamique essaiera d'utiliser le nom donné, ce qui échouera très vraisemblablement (dépendre du répertoire de travail en cours n'est pas fiable).
Si cette séquence ne fonctionne pas, l'extension pour les noms de fichier
des bibliothèques partagées spécifique à la plateforme (souvent
.so
) est ajoutée au nom attribué et la séquence est à
nouveau tentée. En cas de nouvel échec, le chargement échoue.
Il est recommandé de localiser les bibliothèques partagées soit relativement
à $libdir
ou via le chemin dynamique des bibliothèques.
Ceci simplifie les mises à jour de versions si la nouvelle installation est
à un emplacement différent. Le répertoire actuel représenté par
$libdir
est trouvable avec la commande
pg_config --pkglibdir
.
L'identifiant utilisateur sous lequel fonctionne le serveur PostgreSQL doit pouvoir suivre le chemin jusqu'au fichier que vous essayez de charger. Une erreur fréquente revient à définir le fichier ou un répertoire supérieur comme non lisible et/ou non exécutable par l'utilisateur postgres.
Dans tous les cas, le nom de fichier donné dans la commande CREATE
FUNCTION
est enregistré littéralement dans les catalogues
systèmes, de sorte que, si le fichier doit être à nouveau chargé, la même
procédure sera appliquée.
PostgreSQL ne compilera pas une fonction C
automatiquement. Le fichier objet doit être compilé avant d'être référencé
dans une commande CREATE FUNCTION
. Voir la Section 36.10.5 pour des informations complémentaires.
Pour s'assurer qu'un fichier objet chargeable dynamiquement n'est pas chargé
dans un serveur incompatible, PostgreSQL vérifie
que le fichier contient un « bloc magique » avec un contenu
approprié. Ceci permet au serveur de détecter les incompatibilités évidentes
comme du code compilé pour une version majeure différente de
PostgreSQL. Pour inclure un bloc magique,
écrivez ceci dans un (et seulement un) des fichiers source du module, après
avoir inclus l'en-tête fmgr.h
:
PG_MODULE_MAGIC;
Après avoir été utilisé pour la première fois, un fichier objet chargé dynamiquement est conservé en mémoire. Les futurs appels de fonction(s) dans ce fichier pendant la même session provoqueront seulement une légère surcharge due à la consultation d'une table de symboles. Si vous devez forcer le chargement d'un fichier objet, par exemple après une recompilation, commencez une nouvelle session.
De façon optionnelle, un fichier chargé dynamiquement peut contenir une
fonction d'initialisation. Si le fichier inclut une fonction nommée
_PG_init
, cette fonction sera appelée immédiatement
après le chargement du fichier. La fonction ne reçoit aucun paramètre et
doit renvoyer void. Il n'existe actuellement aucun moyen de décharger un
fichier chargé dynamiquement.
Pour savoir comment écrire des fonctions en langage C, vous devez savoir comment PostgreSQL représente en interne les types de données de base et comment elles peuvent être passés vers et depuis les fonctions. En interne, PostgreSQL considère un type de base comme un « blob de mémoire ». Les fonctions que vous définissez sur un type définissent à leur tour la façon que PostgreSQL opère sur lui. C'est-à-dire que PostgreSQL ne fera que conserver et retrouver les données sur le disque et utilisera votre fonction pour entrer, traiter et restituer les données.
Les types de base peuvent avoir un des trois formats internes suivants :
passage par valeur, longueur fixe ;
passage par référence, longueur fixe ;
passage par référence, longueur variable.
Les types par valeur peuvent seulement avoir une longueur de 1, 2 ou 4
octets (également 8 octets si sizeof(Datum)
est de huit
octets sur votre machine). Vous devriez être attentif lors de la
définition de vos types de sorte à qu'ils aient la même taille sur toutes
les architectures. Par exemple, le type long
est
dangereux car il a une taille de quatre octets sur certaines machines et
huit octets sur d'autres, alors que le type int
est de quatre
octets sur la plupart des machines Unix. Une implémentation raisonnable du
type int4
sur une machine Unix pourrait être
/* entier sur quatre octets, passé par valeur */ typedef int int4;
(le code C de PostgreSQL appelle ce type int32
car il
existe une convention en C disant que int
signifie XX
XX
bits. Il est
à noter toutefois que le type C int8
a une taille d'un octet.
Le type SQL int8
est appelé int64
en C. Voir
aussi Tableau 36.2.)
D'autre part, les types à longueur fixe d'une taille quelconque peuvent être passés par référence. Par exemple, voici l'implémentation d'un type PostgreSQL :
/* structure de 16 octets, passée par référence */ typedef struct { double x, y; } Point;
Seuls des pointeurs vers de tels types peuvent être utilisés en les passant
dans et hors des fonctions PostgreSQL. Pour
renvoyer une valeur d'un tel type, allouez la quantité appropriée de
mémoire avec palloc
, remplissez la mémoire allouée et
renvoyez un pointeur vers elle (de plus, si vous souhaitez seulement
renvoyer la même valeur qu'un de vos arguments en entrée qui se trouve du
même type, vous pouvez passer le palloc
supplémentaire et simplement renvoyer le pointeur vers la valeur en
entrée).
Enfin, tous les types à longueur variable doivent aussi être passés par
référence. Tous les types à longueur variable doivent commencer avec un
champ d'une longueur d'exactement quatre octets, qui sera initialisé à
SET_VARSIZE
; ne jamais configurer ce champ directement !
Toutes les données
devant être stockées dans ce type doivent être localisées dans la mémoire à
la suite immédiate de ce champ longueur. Le champ longueur contient la
longueur totale de la structure, c'est-à-dire incluant la longueur du
champ longueur lui-même.
Un autre point important est d'éviter de laisser des bits non initialisés dans les structures de types de données ;; par exemple, prenez bien soin de remplir avec des zéros tous les octets de remplissage qui sont présents dans les structures de données à des fins d'alignement. A défaut, des constantes logiquement équivalentes de vos types de données pourraient être considérées comme inégales par l'optimiseur, impliquant une planification inefficace (bien que les résultats puissent malgré tout être corrects).
Ne jamais modifier le contenu d'une valeur en entrée passée par référence. Si vous le faites, il y a de forts risques pour que vous réussissiez à corrompre les données sur disque car le pointeur que vous avez reçu pourrait bien pointer directement vers un tampon disque. La seule exception à cette règle est expliquée dans la Section 36.12.
Comme exemple, nous pouvons définir le type text
comme
ceci :
typedef struct { int32 length; char data[FLEXIBLE_ARRAY_MEMBER]; } text;
La notation [FLEXIBLE_ARRAY_MEMBER]
signifie que la
longueur actuelle de la donnée n'est pas indiquée par cette déclaration.
En manipulant les types à longueur variable, nous devons être attentifs à
allouer la quantité correcte de mémoire et à fixer correctement le champ
longueur. Par exemple, si nous voulons stocker 40 octets dans une structure
text
, nous devrions utiliser un fragment de code comme
celui-ci :
#include "postgres.h" ... char buffer[40]; /* notre donnée source */ ... text *destination = (text *) palloc(VARHDRSZ + 40); SET_VARSIZE(destination, VARHDRSZ + 40); memcpy(destination->data, buffer, 40); ...
VARHDRSZ
est équivalent à sizeof(int32)
mais
est considéré comme une meilleure tournure de référence à la taille de
l'overhead pour un type à longueur variable.
De plus, le champ de longueur doit être configuré en utilisant
la macro SET_VARSIZE
, pas une simple affectation.
Le Tableau 36.2 spécifie la correspondance entre
les types C et certains des types internes SQL de
PostgreSQL. La colonne « Défini
dans » donne le fichier d'en-tête devant être inclus pour accéder à
la définition du type (la définition effective peut se trouver dans un
fichier différent inclus dans le fichier indiqué. Il est recommandé que les
utilisateurs s'en tiennent à l'interface définie). Notez que vous devriez
toujours inclure postgres.h
en premier dans tout
fichier source du code serveur car il déclare un grand nombre d'éléments
dont vous aurez besoin de toute façon et parce qu'inclure d'autres en-têtes
en premier pourrait causer des problèmes de portabilité.
Tableau 36.2. Équivalence des types C et des types SQL intégrés
Type SQL | Type C | Défini dans |
---|---|---|
boolean | bool | postgres.h (peut-être interne compilateur) |
box | BOX* | utils/geo_decls.h |
bytea | bytea* | postgres.h |
"char" | char | (interne compilateur) |
character | BpChar* | postgres.h |
cid | CommandId | postgres.h |
date | DateADT | utils/date.h |
float4 (real ) | float4 | postgres.h |
float8 (double precision ) | float8 | postgres.h |
int2 (smallint ) | int16 | postgres.h |
int4 (integer ) | int32 | postgres.h |
int8 (bigint ) | int64 | postgres.h |
interval | Interval* | datatype/timestamp.h |
lseg | LSEG* | utils/geo_decls.h |
name | Name | postgres.h |
numeric | Numeric | utils/numeric.h |
oid | Oid | postgres.h |
oidvector | oidvector* | postgres.h |
path | PATH* | utils/geo_decls.h |
point | POINT* | utils/geo_decls.h |
regproc | RegProcedure | postgres.h |
text | text* | postgres.h |
tid | ItemPointer | storage/itemptr.h |
time | TimeADT | utils/date.h |
time with time zone | TimeTzADT | utils/date.h |
timestamp | Timestamp | datatype/timestamp.h |
timestamp with time zone | TimestampTz | datatype/timestamp.h |
varchar | VarChar* | postgres.h |
xid | TransactionId | postgres.h |
Maintenant que nous avons passé en revue toutes les structures possibles pour les types de base, nous pouvons donner quelques exemples de vraies fonctions.
La convention d'appel version-1 repose sur des macros pour supprimer la plus grande partie de la complexité du passage d'arguments et de résultats. La déclaration C d'une fonction en version-1 est toujours :
Datum nom_fonction(PG_FUNCTION_ARGS)
De plus, la macro d'appel :
PG_FUNCTION_INFO_V1(nom_fonction);
doit apparaître dans le même fichier source (par convention, elle est
écrite juste avant la fonction elle-même). Cette macro n'est pas nécessaire
pour les fonctions internal
puisque PostgreSQL
assume que toutes les fonctions internes utilisent la convention version-1.
Elle est toutefois requise pour les fonctions chargées dynamiquement.
Dans une fonction version-1, chaque argument existant est traité par une
macro PG_GETARG_
correspondant au type de donnée de l'argument. (Dans les fonctions non
strictes, il est nécessaire d'avoir une vérification précédente sur la
possibilité que l'argument soit NULL en utilisant
xxx
()PG_ARGISNULL()
; voir ci-dessous.)
Le résultat est renvoyé par une macro
PG_RETURN_
correspondant au type renvoyé.
xxx
()PG_GETARG_
prend comme argument le nombre d'arguments de la fonction à parcourir, le
compteur commençant à 0.
xxx
()PG_RETURN_
prend comme
argument la valeur effective à renvoyer.
xxx
()
Voici quelques exemples utilisant la convention d'appel version-1 :
#include "postgres.h" #include <string.h> #include "fmgr.h" #include "utils/geo_decls.h" #include "varatt.h" PG_MODULE_MAGIC; /* par valeur */ PG_FUNCTION_INFO_V1(add_one); Datum add_one(PG_FUNCTION_ARGS) { int32 arg = PG_GETARG_INT32(0); PG_RETURN_INT32(arg + 1); } /* par référence, longueur fixe */ PG_FUNCTION_INFO_V1(add_one_float8); Datum add_one_float8(PG_FUNCTION_ARGS) { /* La macro pour FLOAT8 cache sa nature de passage par référence. */ float8 arg = PG_GETARG_FLOAT8(0); PG_RETURN_FLOAT8(arg + 1.0); } PG_FUNCTION_INFO_V1(makepoint); Datum makepoint(PG_FUNCTION_ARGS) { /* Ici, la nature de passage par référence de Point n'est pas cachée. */ Point *pointx = PG_GETARG_POINT_P(0); Point *pointy = PG_GETARG_POINT_P(1); Point *new_point = (Point *) palloc(sizeof(Point)); new_point->x = pointx->x; new_point->y = pointy->y; PG_RETURN_POINT_P(new_point); } /* par référence, longueur variable */ PG_FUNCTION_INFO_V1(copytext); Datum copytext(PG_FUNCTION_ARGS) { text *t = PG_GETARG_TEXT_PP(0); /* * VARSIZE_ANY_EXHDR is the size of the struct in bytes, minus the * VARHDRSZ or VARHDRSZ_SHORT of its header. Construct the copy with a * full-length header. */ text *new_t = (text *) palloc(VARSIZE_ANY_EXHDR(t) + VARHDRSZ); SET_VARSIZE(new_t, VARSIZE_ANY_EXHDR(t) + VARHDRSZ); /* * VARDATA is a pointer to the data region of the new struct. The source * could be a short datum, so retrieve its data through VARDATA_ANY. */ memcpy(VARDATA(new_t), /* destination */ VARDATA_ANY(t), /* source */ VARSIZE_ANY_EXHDR(t)); /* how many bytes */ PG_RETURN_TEXT_P(new_t); } PG_FUNCTION_INFO_V1(concat_text); Datum concat_text(PG_FUNCTION_ARGS) { text *arg1 = PG_GETARG_TEXT_PP(0); text *arg2 = PG_GETARG_TEXT_PP(1); int32 arg1_size = VARSIZE_ANY_EXHDR(arg1); int32 arg2_size = VARSIZE_ANY_EXHDR(arg2); int32 new_text_size = arg1_size + arg2_size + VARHDRSZ; text *new_text = (text *) palloc(new_text_size); SET_VARSIZE(new_text, new_text_size); memcpy(VARDATA(new_text), VARDATA_ANY(arg1), arg1_size); memcpy(VARDATA(new_text) + arg1_size, VARDATA_ANY(arg2), arg2_size); PG_RETURN_TEXT_P(new_text); }
En supposant que le code ci-dessus a été enregistré dans le fichier
funcs.c
et compilé en un objet partagé, nous pouvons
définir les fonctions dans PostgreSQL avec les
commandes suivantes :
CREATE FUNCTION add_one(integer) RETURNS integer AS 'DIRECTORY
/funcs', 'add_one' LANGUAGE C STRICT; -- note overloading of SQL function name "add_one" CREATE FUNCTION add_one(double precision) RETURNS double precision AS 'DIRECTORY
/funcs', 'add_one_float8' LANGUAGE C STRICT; CREATE FUNCTION makepoint(point, point) RETURNS point AS 'DIRECTORY
/funcs', 'makepoint' LANGUAGE C STRICT; CREATE FUNCTION copytext(text) RETURNS text AS 'DIRECTORY
/funcs', 'copytext' LANGUAGE C STRICT; CREATE FUNCTION concat_text(text, text) RETURNS text AS 'DIRECTORY
/funcs', 'concat_text' LANGUAGE C STRICT;
Ici, DIRECTORY
indique le répertoire de la
bibliothèque partagée (par exemple le répertoire du tutoriel
PostgreSQL qui contient le code des exemples
utilisés dans cette section). (Il serait préférable d'utiliser seulement
'funcs'
dans la clause AS
, après
avoir ajouté DIRECTORY
au chemin de
recherches. Dans tous les cas, nous pouvons omettre l'extension
spécifique du système pour une bibliothèque partagée, généralement
.so
.)
Notez que nous avons spécifié les fonctions comme « strict »,
ceci signifiant que le système pourrait automatiquement supposer un
résultat NULL si une des valeurs en entrée était NULL. En le faisant,
nous évitons la vérification des entrées NULL dans le code de la
fonction. Sans cela, nous devrions vérifier les valeurs NULL
explicitement en utilisant la macro PG_ARGISNULL()
.
La macro
PG_ARGISNULL(
permet à une fonction
de tester si chaque entrée est NULL (évidemment, ceci n'est nécessaire que
pour les fonctions déclarées non « STRICT »). Comme avec les macros
n
)PG_GETARG_
, les
arguments en entrée sont comptés à partir de zéro. Notez qu'on doit se
garder d'exécuter
xxx
()PG_GETARG_
jusqu'à
ce qu'on ait vérifié que l'argument n'est pas NULL. Pour renvoyer un
résultat NULL, exécutez la fonction
xxx
()PG_RETURN_NULL()
; ceci convient aussi bien dans
les fonctions STRICT que non STRICT.
Au premier coup d'œud, les conventions de codage version-1
pourraient ressembler à de l'obscurantisme sans raison. Il pourrait
sembler préférable d'utiliser les conventions d'appel
C
. Néanmoins, elles permettent de gérer des valeurs
NULL pour les arguments et la valeur de retour calling, ainsi que des
valeurs TOAST (compressées ou hors-ligne).
Les autres options proposées dans l'interface version 1 sont deux
variantes des macros
PG_GETARG_
. La
première d'entre elles,
xxx
()PG_GETARG_
,
garantit le renvoi d'une copie de l'argument spécifié où nous pouvons
écrire en toute sécurité (les macros normales peuvent parfois renvoyer
un pointeur vers une valeur physiquement mise en mémoire dans une table qui
ne doit pas être modifiée. En utilisant les macros
xxx
_COPY()PG_GETARG_
, on
garantit l'écriture du résultat). La seconde variante se compose des macros
xxx
_COPY()PG_GETARG_
qui prennent trois arguments. Le premier est le nombre d'arguments de la
fonction (comme ci-dessus). Le second et le troisième sont le décalage et
la longueur du segment qui doit être renvoyé. Les décalages sont comptés à
partir de zéro et une longueur négative demande le renvoi du reste de la
valeur. Ces macros procurent un accès plus efficace à des parties de
valeurs à grande dimension dans le cas où elles ont un type de stockage en
mémoire « external » (le type de stockage d'une colonne peut
être spécifié en utilisant xxx
_SLICE()ALTER TABLE
.
nom_table
ALTER COLUMN
nom_colonne
SET STORAGE
typestockage
typestockage
est un type parmi
plain
, external
, extended
ou
main
).
Enfin, les conventions d'appels de la version-1 rendent possible le renvoi
de résultats d'ensemble (Section 36.10.8),
l'implémentation de fonctions triggers (Chapitre 37) et d'opérateurs d'appel de langage procédural (Chapitre 56). Pour plus de détails, voir
src/backend/utils/fmgr/README
dans les fichiers
sources de la distribution.
Avant de nous intéresser à des sujets plus avancés, nous devons discuter de quelques règles de codage des fonctions en langage C de PostgreSQL. Bien qu'il soit possible de charger des fonctions écrites dans des langages autre que le C dans PostgreSQL, c'est habituellement difficile (quand c'est possible) parce que les autres langages comme C++, FORTRAN ou Pascal ne suivent pas fréquemment les mêmes conventions de nommage que le C. C'est-à-dire que les autres langages ne passent pas les arguments et ne renvoient pas les valeurs entre fonctions de la même manière. Pour cette raison, nous supposerons que nos fonctions en langage C sont réellement écrites en C.
Les règles de base pour l'écriture de fonctions C sont les suivantes :
Utilisez pg_config
--includedir-server
pour découvrir où
sont installés les fichiers d'en-tête du serveur
PostgreSQL sur votre système (ou sur le système de vos
utilisateurs).
Compilez et liez votre code de façon à ce qu'il soit chargé dynamiquement dans PostgreSQL, ce qui requiert des informations spéciales. Voir Section 36.10.5 pour une explication détaillée sur la façon de le faire pour votre système d'exploitation spécifique.
Rappelez-vous de définir un « bloc magique » pour votre bibliothèque partagée, comme décrit dans Section 36.10.1.
Quand vous allouez de la mémoire, utilisez les fonctions
PostgreSQL
palloc
et
pfree
au lieu des fonctions correspondantes malloc
et
free
de la bibliothèque C. La mémoire allouée par
palloc
sera libérée automatiquement à la fin de
chaque transaction, empêchant des débordements de mémoire.
Remettez toujours à zéro les octets de vos structures en utilisant
memset
(ou allouez les avec la fonction palloc0
).
Même si vous assignez chacun des champs de votre structure, il
pourrait rester des espaces de remplissage (trous dans la structure)
afin de respecter l'alignement des données qui contiennent des
valeurs parasites. Sans cela, il sera difficile de calculer des hachages
pour les index ou les jointures, dans la mesure où vous devrez
uniquement tenir compte des octets significatifs de vos structures
de données pour calculer ces hachages.
Le planificateur se base également sur des comparaisons de constantes
via des égalités de bits, aussi vous pouvez obtenir des planifications
incorrectes si des valeurs logiquement équivalentes ne sont pas
identiques bit à bit.
La plupart des types internes PostgreSQL
sont déclarés dans postgres.h
alors que les
interfaces de gestion des fonctions
(PG_FUNCTION_ARGS
, etc.) sont dans
fmgr.h
. Du coup, vous aurez besoin d'inclure au
moins ces deux fichiers. Pour des raisons de portabilité, il vaut
mieux inclure postgres.h
en premier
avant tout autre fichier d'en-tête système ou utilisateur. En incluant
postgres.h
, il incluera également
elog.h
et palloc.h
pour vous.
Les noms de symboles définis dans les objets ne doivent pas entrer en conflit entre eux ou avec les symboles définis dans les exécutables du serveur PostgreSQL. Vous aurez à renommer vos fonctions ou variables si vous recevez un message d'erreur à cet effet.
Avant de pouvoir être utilisées dans PostgreSQL, les fonctions d'extension écrites en C doivent être compilées et liées d'une certaine façon, ceci afin de produire un fichier dynamiquement chargeable par le serveur. Pour être plus précis, une bibliothèque partagée doit être créée.
Pour obtenir plus d'informations que celles contenues dans cette section,
il faut se référer à la documentation du système d'exploitation, en
particulier les pages traitant du compilateur C, de cc
et
de l'éditeur de lien, ld
. Par ailleurs, le code
source de PostgreSQL contient de nombreux exemples
fonctionnels dans le répertoire contrib
.
Néanmoins, ces exemples entraînent la création de modules
qui dépendent de la disponibilité du code source de
PostgreSQL.
La création de bibliothèques partagées est un processus analogue à celui utilisé pour lier des exécutables : les fichiers sources sont d'abord compilés en fichiers objets puis sont liées ensemble. Les fichiers objets doivent être compilés sous la forme de code indépendant de sa position (PIC, acronyme de position-independent code) . Conceptuellement, cela signifie qu'ils peuvent être placés dans une position arbitraire de la mémoire lorsqu'ils sont chargés par l'exécutable. (Les fichiers objets destinés aux exécutables ne sont généralement pas compilés de cette manière.) La commande qui permet de lier des bibliothèques partagées nécessite des options spéciales qui la distinguent de celle permettant de lier un exécutable. En théorie, tout du moins. La réalité est, sur certains systèmes, beaucoup plus complexe.
Les exemples suivants considèrent que le code source est un
fichier foo.c
et qu'une bibliothèque partagée
foo.so
doit être créée. Sans précision, le fichier objet
intermédiaire est appelé foo.o
. Une bibliothèque
partagée peut contenir plusieurs fichiers objet. Cela dit, un seul est
utilisé ici.
L'option du compilateur pour créer des PIC est
-fPIC
. L'option de l'éditeur de liens pour créer des
bibliothèques partagées est -shared
.
cc -fPIC -c foo.c cc -shared -o foo.so foo.o
Ceci est applicable depuis la version 13.0 de
FreeBSD, les versions
plus anciennes utilisent le compilateur
gcc
.
L'option du compilateur pour créer des PIC est
-fPIC
. L'option de compilation pour créer des
bibliothèques partagées est -shared
. Un exemple complet
ressemble à :
cc -fPIC -c foo.c cc -shared -o foo.so foo.o
L'exemple suivant suppose que les outils de développement sont installés.
cc -c foo.c cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o
L'option du compilateur pour créer des PIC est
-fPIC
. Pour les systèmes ELF,
l'option de compilation pour lier les bibliothèques partagées
est -shared
. Sur les systèmes plus anciens et non-ELF,
on utilise ld -Bshareable
.
gcc -fPIC -c foo.c gcc -shared -o foo.so foo.o
L'option du compilateur pour créer des PIC est
-fPIC
. Les bibliothèques partagées peuvent être créées avec
ld -Bshareable
.
gcc -fPIC -c foo.c ld -Bshareable -o foo.so foo.o
L'option du compilateur pour créer des PIC est
-KPIC
avec le compilateur de Sun et -fPIC
avec GCC. Pour lier les bibliothèques
partagées, l'option de compilation est respectivement -G
ou -shared
.
cc -KPIC -c foo.c cc -G -o foo.so foo.o
ou
gcc -fPIC -c foo.c gcc -G -o foo.so foo.o
Si cela s'avère trop compliqué, GNU Libtool peut être utilisé. Cet outil permet de s'affranchir des différences entre les nombreux systèmes au travers d'une interface uniformisée.
La bibliothèque partagée résultante peut être chargée dans
PostgreSQL. Lorsque l'on précise le nom du fichier
dans la commande CREATE FUNCTION
, il faut indiquer le nom
de la bibliothèque partagée et non celui du fichier objet intermédiaire.
L'extension standard pour les bibliothèques partagées (en général
.so
ou .sl
) peut être omise dans la commande
CREATE FUNCTION
, et doit l'être pour une meilleure
portabilité.
La Section 36.10.1 indique l'endroit où le serveur s'attend à trouver les fichiers de bibliothèques partagées.
Les types composites n'ont pas une organisation fixe comme les structures en C. Des instances d'un type composite peuvent contenir des champs NULL. De plus, les types composites faisant partie d'une hiérarchie d'héritage peuvent avoir des champs différents des autres membres de la même hiérarchie. En conséquence, PostgreSQL propose une interface de fonction pour accéder depuis le C aux champs des types composites.
Supposons que nous voulions écrire une fonction pour répondre à la requête :
SELECT nom, c_surpaye(emp, 1500) AS surpaye FROM emp WHERE nom = 'Bill' OR nom = 'Sam';
En utilisant les conventions d'appel de la version 1, nous pouvons définir
c_surpaye
comme :
#include "postgres.h" #include "executor/executor.h" /* pour GetAttributeByName() */ PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(c_surpaye); Datum c_surpaye(PG_FUNCTION_ARGS) { HeapTupleHeader *t = (HeapTupleHeader *) PG_GETARG_HEAPTUPLEHEADER(0); int32 limite = PG_GETARG_INT32(1); bool isNULL; Datum salaire; salaire = GetAttributeByName(t, "salaire", &isNULL); if (isNULL) PG_RETURN_BOOL(false); /* Autrement, nous pourrions préférer de lancer PG_RETURN_NULL() pour un salaire NULL. */ PG_RETURN_BOOL(DatumGetInt32(salaire) > limite); }
GetAttributeByName
est la fonction système
PostgreSQL qui renvoie les attributs depuis une
colonne spécifiée. Elle a trois arguments : l'argument de type
HeapTupleHeader
passé à la fonction, le nom de l'attribut
recherché et un paramètre de retour qui indique si l'attribut est NULL.
GetAttributeByName
renvoie une valeur de type
Datum
que vous pouvez convertir dans un type voulu en
utilisant la fonction appropriée
DatumGet
. Notez que
la valeur de retour est insignifiante si le commutateur NULL est
positionné ; il faut toujours vérifier le commutateur NULL avant de commencer
à faire quelque chose avec le résultat.
XXX
()
Il y a aussi GetAttributeByNum
, qui sélectionne
l'attribut cible par le numéro de colonne au lieu de son nom.
La commande suivante déclare la fonction c_surpaye
en SQL :
CREATE FUNCTION c_surpaye(emp, integer) RETURNS boolean
AS 'DIRECTORY
/funcs', 'c_surpaye'
LANGUAGE C STRICT;
Notez que nous avons utilisé STRICT
pour que nous n'ayons pas à
vérifier si les arguments en entrée sont NULL.
Pour renvoyer une ligne ou une valeur de type composite à partir d'une fonction en langage C, vous pouvez utiliser une API spéciale qui fournit les macros et les fonctions dissimulant en grande partie la complexité liée à la construction de types de données composites. Pour utiliser cette API, le fichier source doit inclure :
#include "funcapi.h"
Il existe deux façons de construire une valeur de données composites
(autrement dit un « tuple ») : vous pouvez le construire à
partir d'un tableau de valeurs Datum ou à partir d'un tableau de
chaînes C qui peuvent passer dans les fonctions de conversion des types
de données du tuple. Quelque soit le cas, vous avez d'abord besoin
d'obtenir et de construire un descripteur TupleDesc
pour
la structure du tuple. En travaillant avec des Datums, vous passez le
TupleDesc
à BlessTupleDesc
, puis vous appelez
heap_form_tuple
pour chaque ligne. En travaillant avec des
chaînes C, vous passez TupleDesc
à
TupleDescGetAttInMetadata
, puis vous appelez
BuildTupleFromCStrings
pour chaque ligne. Dans le cas d'une
fonction renvoyant un ensemble de tuple, les étapes de configuration
peuvent toutes être entreprises une fois lors du premier appel à la
fonction.
Plusieurs fonctions d'aide sont disponibles pour configurer le
TupleDesc
requis. La façon recommandée de le faire dans la
plupart des fonctions renvoyant des valeurs composites est d'appeler :
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo, Oid *resultTypeId, TupleDesc *resultTupleDesc)
en passant la même structure fcinfo
que celle passée à la
fonction appelante (ceci requiert bien sûr que vous utilisez les
conventions d'appel version-1). resultTypeId
peut être
spécifié comme NULL
ou comme l'adresse d'une variable locale
pour recevoir l'OID du type de résultat de la fonction.
resultTupleDesc
devrait être l'adresse d'une variable
TupleDesc
locale. Vérifiez que le résultat est
TYPEFUNC_COMPOSITE
; dans ce cas,
resultTupleDesc
a été rempli avec le
TupleDesc
requis (si ce n'est pas le cas, vous pouvez
rapporter une erreur pour une « fonction renvoyant un enregistrement
appelé dans un contexte qui ne peut pas accepter ce type
enregistrement »).
get_call_result_type
peut résoudre le vrai type du
résultat d'une fonction polymorphique ; donc, il est utile pour les
fonctions qui renvoient des résultats scalaires polymorphiques, pas
seulement les fonctions qui renvoient des types composites. Le résultat
resultTypeId
est principalement utile pour les fonctions
renvoyant des scalaires polymorphiques.
get_call_result_type
a une fonction cousine
get_expr_result_type
, qui peut être utilisée pour résoudre
le tupe attendu en sortie en un appel de fonction représenté par
un arbre d'expressions. Ceci peut être utilisé pour tenter de déterminer
le type de résultat sans entrer dans la fonction elle-même. Il existe
aussi get_func_result_type
, qui peut seulement être utilisée
quand l'OID de la fonction est disponible. Néanmoins, ces fonctions ne
sont pas capables de gérer les fonctions déclarées renvoyer des
enregistrements (record
).
get_func_result_type
ne peut pas résoudre les types
polymorphiques, donc vous devriez utiliser de préférence
get_call_result_type
.
Les fonctions anciennes, et maintenant obsolètes, qui permettent d'obtenir des
TupleDesc
sont :
TupleDesc RelationNameGetTupleDesc(const char *relname)
pour obtenir un TupleDesc
pour le type de ligne d'une
relation nommée ou :
TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)
pour obtenir une TupleDesc
basée sur l'OID d'un type. Ceci
peut être utilisé pour obtenir un TupleDesc
soit pour un
type de base, soit pour un type composite. Néanmoins, cela ne fonctionnera
pas pour une fonction qui renvoie record
et cela ne résoudra
pas les types polymorphiques.
Une fois que vous avez un TupleDesc
, appelez :
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
si vous pensez travailler avec des Datums ou :
AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)
si vous pensez travailler avec des chaînes C. Si vous écrivez une
fonction renvoyant un ensemble, vous pouvez sauvegarder les résultats
de ces fonctions dans la structure dans le
FuncCallContext
-- utilisez le champ
tuple_desc
ou attinmeta
respectivement.
Lorsque vous fonctionnez avec des Datums, utilisez :
HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)
pour construire une donnée utilisateur HeapTuple
indiquée
dans le format Datum.
Lorsque vous travaillez avec des chaînes C, utilisez :
HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
pour construire une donnée utilisateur HeapTuple
indiquée
dans le format des chaînes C. values
est un tableau de
chaîne C, une pour chaque attribut de la ligne renvoyée. Chaque chaîne
C doit être de la forme attendue par la fonction d'entrée du type de
donnée de l'attribut. Afin de renvoyer une valeur NULL pour un des
attributs, le pointeur correspondant dans le tableau de valeurs
(values
) doit être fixé à NULL
. Cette fonction
demandera à être appelée pour chaque ligne que vous renvoyez.
Une fois que vous avez construit un tuple devant être renvoyé par votre
fonction, vous devez le convertir en type Datum
. Utilisez :
HeapTupleGetDatum(HeapTuple tuple)
pour convertir un type HeapTuple
en un Datum valide.
Ce Datum
peut être renvoyé directement si vous envisagez de
renvoyer juste une simple ligne ou bien il peut être utilisé pour renvoyer
la valeur courante dans une fonction renvoyant un ensemble.
Un exemple figure dans la section suivante.
Les fonctions en langage C ont deux options pour renvoyer des ensembles (plusieurs lignes). Dans la première méthode, appelée mode ValuePerCall, une fonction renvoyant un ensemble de lignes est appelée de façon répétée (en passant les mêmes arguments à chaque fois) et elle renvoit une nouvelle ligne pour chaque appel, jusqu'à ce qu'il n'y ait plus de lignes à renvoyer et qu'elle le signale en renvoyant NULL. La fonction SRF doit de ce fait sauvegarder l'état entre appels pour se rappeler ce qu'elle faisait et renvoyer le bon prochain élément à chaque appel. Dans l'autre méthode, appelée mode Materialize, une SRF remplit et renvoit un objet tuplestore contenant le résultat entier. Un seul appel survient pour le résultat complet, et il n'est pas nécessaire de conserver l'état entre appels.
Lors de l'utilisation du mode ValuePerCall, il est important de se
rappeler que la requête n'est pas garantie de se terminer ;
c'est-à-dire, avec des options telles que LIMIT
,
l'exécuteur pourrait stopper les appels à la fonction SRF avant que
toutes les lignes ne soient récupérées. Ceci signifie qu'il ne faut pas
utiliser le dernier appel pour nettoyer l'activité réalisée, car ce
dernier appel pourrait bien ne jamais survenir. Il est recommandé
d'utiliser le mode Materialize pour les fonctions ayant besoin d'accéder
aux ressources externes, tels que des descripteurs de fichiers.
Le reste de cette section documente un ensemble de macros d'aide qui
sont communément utilisées (bien que non requis pour être utilisés) pour
les SRF utilisant le mode ValuePerCall. Des détails supplémentaires sur
le mode Materialize peuvent être trouvés dans
src/backend/utils/fmgr/README
. De plus, les modules
contrib
dans la distribution des sources de the
PostgreSQL contiennent de nombreux exemples
de SRF utilisant à la fois les modes ValuePerCall et Materialize.
Pour utiliser les macros de support ValuePerCall décrites ici, inclure
funcapi.h
. Ces macros fonctionnent avec une
structure FuncCallContext
qui contient l'état
devant être sauvegardé au travers des appels. À l'intérieur de la SRF
appelante, fcinfo->flinfo->fn_extra
est utilisé
pour détenir un pointeur vers FuncCallContext
entre les appels. Les macros remplissent automatiquement ce champ à la
première utilisation et s'attendent à y trouver le même pointeur lors
des prochains appels.
typedef struct FuncCallContext { /* * Number of times we've been called before * * call_cntr is initialized to 0 for you by SRF_FIRSTCALL_INIT(), and * incremented for you every time SRF_RETURN_NEXT() is called. */ uint64 call_cntr; /* * OPTIONAL maximum number of calls * * max_calls is here for convenience only and setting it is optional. * If not set, you must provide alternative means to know when the * function is done. */ uint64 max_calls; /* * OPTIONAL pointer to miscellaneous user-provided context information * * user_fctx is for use as a pointer to your own data to retain * arbitrary context information between calls of your function. */ void *user_fctx; /* * OPTIONAL pointer to struct containing attribute type input metadata * * attinmeta is for use when returning tuples (i.e., composite data types) * and is not used when returning base data types. It is only needed * if you intend to use BuildTupleFromCStrings() to create the return * tuple. */ AttInMetadata *attinmeta; /* * memory context used for structures that must live for multiple calls * * multi_call_memory_ctx is set by SRF_FIRSTCALL_INIT() for you, and used * by SRF_RETURN_DONE() for cleanup. It is the most appropriate memory * context for any memory that is to be reused across multiple calls * of the SRF. */ MemoryContext multi_call_memory_ctx; /* * OPTIONAL pointer to struct containing tuple description * * tuple_desc is for use when returning tuples (i.e. composite data types) * and is only needed if you are going to build the tuples with * heap_form_tuple() rather than with BuildTupleFromCStrings(). Note that * the TupleDesc pointer stored here should usually have been run through * BlessTupleDesc() first. */ TupleDesc tuple_desc; } FuncCallContext;
Les macros à utiliser par une SRF utilisant cette infrastructure sont :
SRF_IS_FIRSTCALL()
Utilisez ceci pour déterminer si votre fonction est appelée pour la première fois ou la prochaine fois. Au premier appel (seulement), utilisez :
SRF_FIRSTCALL_INIT()
pour initialiser la structure FuncCallContext
.
À chaque appel de fonction, y compris le premier, utilisez :
SRF_PERCALL_SETUP()
pour configurer l'utilisation de FuncCallContext
.
Si votre fonction a des données à renvoyer dans l'appel courant, utilisez :
SRF_RETURN_NEXT(funcctx, result)
pour les renvoyer à l'appelant. (result
doit être de type
Datum
, soit une valeur simple, soit un tuple préparé comme décrit
ci-dessus.) Enfin, quand votre fonction a fini de renvoyer des données,
utilisez :
SRF_RETURN_DONE(funcctx)
pour nettoyer et terminer la SRF.
Lors de l'appel de la SRF, le contexte mémoire courant est un
contexte transitoire qui est effacé entre les appels. Cela signifie que
vous n'avez pas besoin d'appeler pfree
sur tout ce que vous
avez alloué en utilisant palloc
; ce sera supprimé de
toute façon. Toutefois, si vous voulez allouer des structures de données
devant persister tout au long des appels, vous avez besoin de les conserver
quelque part. Le contexte mémoire référencé par
multi_call_memory_ctx
est un endroit approprié pour toute
donnée devant survivre jusqu'à l'achèvement de la fonction SRF.
Dans la plupart des cas, cela signifie que vous devrez basculer vers
multi_call_memory_ctx
au moment de la préparation du
premier appel. Utilisez funcctx->user_fctx
pour
récupérer un pointeur vers de telles structures de données inter-appels
(les données que vous allouez dans
multi_call_memory_ctx
partiront
automatiquement à la fin de la requête, donc il n'est pas nécessaire de
libérer cette donnée manuellement).
Quand les arguments réels de la fonction restent inchangés entre les
appels, si vous lisez la valeur des arguments (ce qui se fait de façon
transparente par la macro
PG_GETARG_
) dans le
contexte, alors les copies seront libérées sur chaque cycle. De la même
façon, si vous conservez des références vers de telles valeurs dans
votre xxx
user_fctx
, vous devez soit les copier
dans multi_call_memory_ctx
, soit vous
assurer que vous procédez vous-même au traitement des valeurs dans ce
contexte.
Voici un exemple complet de pseudo-code :
Datum my_set_returning_function(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; Datum result;further declarations as needed
if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* One-time setup code appears here: */user code
if returning composite
build TupleDesc, and perhaps AttInMetadata
endif returning composite
user code
MemoryContextSwitchTo(oldcontext); } /* Each-time setup code appears here: */user code
funcctx = SRF_PERCALL_SETUP();user code
/* this is just one way we might test whether we are done: */ if (funcctx->call_cntr < funcctx->max_calls) { /* Here we want to return another item: */user code
obtain result Datum
SRF_RETURN_NEXT(funcctx, result); } else { /* Here we are done returning items, so just report that fact. */ /* (Resist the temptation to put cleanup code here.) */ SRF_RETURN_DONE(funcctx); } }
Et voici un exemple complet d'une simple SRF retournant un type composite :
PG_FUNCTION_INFO_V1(retcomposite); Datum retcomposite(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int call_cntr; int max_calls; TupleDesc tupdesc; AttInMetadata *attinmeta; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; /* create a function context for cross-call persistence */ funcctx = SRF_FIRSTCALL_INIT(); /* switch to memory context appropriate for multiple function calls */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* total number of tuples to be returned */ funcctx->max_calls = PG_GETARG_INT32(0); /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); /* * generate attribute metadata needed later to produce tuples from raw * C strings */ attinmeta = TupleDescGetAttInMetadata(tupdesc); funcctx->attinmeta = attinmeta; MemoryContextSwitchTo(oldcontext); } /* stuff done on every call of the function */ funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; attinmeta = funcctx->attinmeta; if (call_cntr < max_calls) /* do when there is more left to send */ { char **values; HeapTuple tuple; Datum result; /* * Prepare a values array for building the returned tuple. * This should be an array of C strings which will * be processed later by the type input functions. */ values = (char **) palloc(3 * sizeof(char *)); values[0] = (char *) palloc(16 * sizeof(char)); values[1] = (char *) palloc(16 * sizeof(char)); values[2] = (char *) palloc(16 * sizeof(char)); snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1)); snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1)); snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1)); /* build a tuple */ tuple = BuildTupleFromCStrings(attinmeta, values); /* make the tuple into a datum */ result = HeapTupleGetDatum(tuple); /* clean up (this is not really necessary) */ pfree(values[0]); pfree(values[1]); pfree(values[2]); pfree(values); SRF_RETURN_NEXT(funcctx, result); } else /* do when there is no more left */ { SRF_RETURN_DONE(funcctx); } }
Voici une façon de déclarer cette fonction en SQL :
CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer);
CREATE OR REPLACE FUNCTION retcomposite(integer, integer)
RETURNS SETOF __retcomposite
AS 'filename
', 'retcomposite'
LANGUAGE C IMMUTABLE STRICT;
Une façon différente de le faire est d'utiliser des paramètres OUT :
CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
OUT f1 integer, OUT f2 integer, OUT f3 integer)
RETURNS SETOF record
AS 'filename
', 'retcomposite'
LANGUAGE C IMMUTABLE STRICT;
Notez que dans cette méthode le type en sortie de la fonction est du type
record
anonyme.
Les fonctions en langage C peuvent être déclarées pour accepter et renvoyer
les types « polymorphes » décrits dans Section 36.2.5. Si les types des
arguments ou du renvoi de la fonction sont définis comme polymorphes,
l'auteur de la fonction ne peut pas savoir à l'avance quel type de données
sera appelé ou bien quel type doit être renvoyé. Il y a deux routines
offertes par fmgr.h
qui permettent à une fonction en
version-1 de découvrir les types de données effectifs de ses arguments et
le type qu'elle doit renvoyer. Ces routines s'appellent
get_fn_expr_rettype(FmgrInfo *flinfo)
et
get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
. Elles
renvoient l'OID du type du résultat ou de l'argument ou InvalidOID si
l'information n'est pas disponible. L'accès à la structure
flinfo
se fait normalement avec
fcinfo->flinfo
. Le paramètre argnum
est basé à
partir de zéro. get_call_result_type
peut aussi être utilisé
comme alternative à get_fn_expr_rettype
.
Il existe aussi get_fn_expr_variadic
, qui peut être
utilisé pour trouver les arguments variables en nombre qui ont été assemblés
en un tableau. C'est principalement utile dans le cadre des fonctions
VARIADIC "any"
car de tels assemblages surviendront
toujours pour les fonctions variadiques prenant des types de tableaux
ordinaires.
Par exemple, supposons que nous voulions écrire une fonction qui accepte un argument de n'importe quel type et qui renvoie un tableau uni-dimensionnel de ce type :
PG_FUNCTION_INFO_V1(make_array); Datum make_array(PG_FUNCTION_ARGS) { ArrayType *result; Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 0); Datum element; bool isnull; int16 typlen; bool typbyval; char typalign; int ndims; int dims[MAXDIM]; int lbs[MAXDIM]; if (!OidIsValid(element_type)) elog(ERROR, "could not determine data type of input"); /* get the provided element, being careful in case it's NULL */ isnull = PG_ARGISNULL(0); if (isnull) element = (Datum) 0; else element = PG_GETARG_DATUM(0); /* we have one dimension */ ndims = 1; /* and one element */ dims[0] = 1; /* and lower bound is 1 */ lbs[0] = 1; /* get required info about the element type */ get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); /* now build the array */ result = construct_md_array(&element, &isnull, ndims, dims, lbs, element_type, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result); }
La commande suivante déclare la fonction make_array
en
SQL :
CREATE FUNCTION make_array(anyelement)
RETURNS anyarray
AS 'DIRECTORY
/funcs', 'make_array'
LANGUAGE 'C' IMMUTABLE;
Notez l'utilisation de STRICT ; ceci est primordial car le code ne se préoccupe pas de tester une entrée NULL.
Il existe une variante du polymorphisme qui est seulement disponible pour
les fonctions en langage C : elles peuvent être déclarées prendre des
paramètres de type "any"
. (Notez que ce nom de type doit
être placé entre des guillemets doubles car il s'agit d'un mot SQL réservé.)
Ceci fonctionne comme anyelement
sauf qu'il ne contraint pas
les différents arguments "any"
à être du même type, pas
plus qu'ils n'aident à déterminer le type de résultat de la fonction. Une
fonction en langage C peut aussi déclarer son paramètre final ainsi :
VARIADIC "any"
. Cela correspondra à un ou plusieurs
arguments réels de tout type (pas nécessairement le même type). Ces
arguments ne seront pas placés dans un tableau
comme c'est le cas pour les fonctions variadic normales ; ils seront
passés séparément à la fonction. La macro PG_NARGS()
et les méthodes décrites ci-dessus doivent être utilisées pour déterminer
le nombre d'arguments réels et leur type lors de l'utilisation de cette
fonctionnalité. Ainsi, les utilisateurs d'une telle fonction voudront
probablement utilisé le mot-clé VARIADIC
dans leur
appel de fonction, de manière à ce que la fonction traite les éléments
du tableau comme des arguments séparés. La fonction elle-même doit
implémenter ce comportement si nécessaire, après avoir utilisé
get_fn_expr_variadic
pour savoir si les arguments
actuels ont été marqués avec VARIADIC
.
Les modules peuvent réserver de la mémoire au démarrage du serveur. Pour
cela, la bibliothèque partagée du module doit être préchargée en
l'ajoutant dans xref
linkend="guc-shared-preload-libraries"/>.
La bibliothèque partagée doit aussi enregistrer une fonction hook
shmem_request_hook
dans sa fonction
_PG_init
. Cette fonction
shmem_request_hook
peut réserver de la mémoire
partagée en appelant :
void RequestAddinShmemSpace(Size size)
Chaque processus doit obtenir un pointeur vers la mémoire partagée réservée en appelant :
void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
Si cette fonction configure foundPtr
à
false
, l'appelant doit réaliser l'initialisation du
contenu de la mémoire partagée réservée. Si foundPtr
vaut true
, la mémoire partagée était déjà initialisée
par un autre processus et l'appelant n'a plus besoin de rien faire
concernant l'initialisation.
Pour éviter tout cas problématique, tout processus doit utiliser le verrou
LWLock AddinShmemInitLock
lors de l'initialisation de
l'allocation de mémoire partagée, comme montré ici :
static mystruct *ptr = NULL; bool found; LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); ptr = ShmemInitStruct("my struct name", size, &found); if (!found) { ... initialize contents of shared memory ... ptr->locks = GetNamedLWLockTranche("my tranche name"); } LWLockRelease(AddinShmemInitLock);
shmem_startup_hook
fournit un emplacement correct pour
le code d'initialisation mais il n'est pas strictement requis qu'un tel
code soit placé dans cette fonction hook. Chaque processus executera
le shmem_startup_hook
enregistré un peu après s'être
attaché à la mémoire partagée. Notez que les modules doivent toujours
acquérir AddinShmemInitLock
dans ce hook, comme
indiqué dans l'exemple ci-dessus.
Un exemple d'un shmem_request_hook
et d'un
shmem_startup_hook
peut se trouver dans
contrib/pg_stat_statements/pg_stat_statements.c
à
partir du répertoire des sources de PostgreSQL.
Il existe une autre méthode, plus flexible, pour réserver de la mémoire
partagée. Elle se fait après le démarrage du serveur et en dehors d'un
shmem_request_hook
. Pour cela, chaque processus qui
doit utiliser la mémoire partager doit obtenir un pointeur en
appelant :
void *GetNamedDSMSegment(const char *name, size_t size, void (*init_callback) (void *ptr), bool *found)
Si un segment de mémoire partagée dynamique de ce nom n'existe pas encore,
cette fonction l'allouera et l'initialisera avec la fonction callback
fournie init_callback
. Si le segment a été alloué et
initialisé par un autre processus, cette fonction attache simplement le
segment de mémoire partagée dynamique au processus courant..
Contrairement à la mémoire partagée réservée au démarrage du serveur, il
n'est plus nécessaire d'acquérir AddinShmemInitLock
ou de réaliser des actions pour éviter les cas spéciaux lors de la
réservation de mémoire partagée avec
GetNamedDSMSegment
. Cette fonction assure que seul
un processus alloue et initialise le segment et que tous les autres
processus reçoivent un pointeur vers le segment alloué et initialisé.
Une utilisation complète de GetNamedDSMSegment
est
disponible dans
src/test/modules/test_dsm_registry/test_dsm_registry.c
à partir du répertoire des sources de PostgreSQL.
Les modules peuvent réserver des LWLocks au démarrage du serveur. Comme
avec la mémoire partagée réservée au démarrage, la bibliothèque partagée
du module doit être préchargée en l'ajoutant dans
shared_preload_libraries,
et la bibliothèque partagée doit enregistrer une fonction
shmem_request_hook
dans sa fonction
_PG_init
. Cette fonction
shmem_request_hook
peut réserver des LWLocks en
appelant :
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
Ceci assure qu'un tableau de num_lwlocks
LWLocks est
disponible sous le nom tranche_name
. Un pointeur vers
ce tableau peut être obtenu en appelant :
LWLockPadded *GetNamedLWLockTranche(const char *tranche_name)
Il existe une autre méthode, plus flexible, d'obtenir des LWLocks qui peut
se faire après le démarrage du serveur et en dehors d'une fonction
shmem_request_hook
. Pour cela, allouez tout d'abord un
tranche_id
en appelant :
int LWLockNewTrancheId(void)
Puis initialisez chaque LWLock, en lui passant le nouveau
tranche_id
comme argument :
void LWLockInitialize(LWLock *lock, int tranche_id)
De façon similaire à la mémoire partagée, chaque processus doit s'assurer
qu'un seul processus alloue un nouveau tranche_id
et
initialise chaque nouveau LWLock. Une façon de le faire est de seulement
appeler ces fonctions dans le code d'initialisation de la mémoire partagée
avec AddinShmemInitLock
détenu en exclusivité. Si
vous utilisez GetNamedDSMSegment
, appeler ces
fonctions dans la fonction callback init_callback
est suffisant pour éviter les cas problématiques rares.
Enfin, chaque processus utilisant tranche_id
doit
l'associer avec un tranche_name
en appelant :
void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
Un exemple complet d'utilisation de LWLockNewTrancheId
,
LWLockInitialize
et
LWLockRegisterTranche
peut être trouvé dans
contrib/pg_prewarm/autoprewarm.c
à partir du
répertoire des sources de PostgreSQL.
Les modules peuvent définir des événements d'attente sous le type
d'événement Extension
en appelant :
uint32 WaitEventExtensionNew(const char *wait_event_name)
L'événement d'attente est associé à une chaîne personnalisée visible par
l'utilisateur. Un exemple est disponible dans
src/test/modules/worker_spi
à partir du répertoire
des sources de PostgreSQL.
Les événements d'attente personnalisés sont visibles dans
pg_stat_activity
:
=# SELECT wait_event_type, wait_event FROM pg_stat_activity WHERE backend_type ~ 'worker_spi'; wait_event_type | wait_event -----------------+--------------- Extension | WorkerSpiMain (1 row)
Un point d'injection avec un name
donné est déclaré
en utilisant la macro :
INJECTION_POINT(name);
Il existe quelques points d'injection déjà déclarés à des endroits stratégiques du code du serveur. Après l'ajout d'un nouveau point d'injection, le code a besoin d'être compilé pour que le point d'injection soit disponible dans le binaire. Les modules écrits en langage C peuvent déclarer des points d'injection dans leur propre code en utilisant la même macro.
Les modules peuvent attacher des callbacks à un point d'injection déjà déclaré en appelant :
extern void InjectionPointAttach(const char *name, const char *library, const char *function, const void *private_data, int private_data_size);
name
est le nom du point d'injection qui, lorsqu'il est
atteint à l'exécutionn exécutera la fonction function
chargé à partir de la bibliothèque library
.
private_data
est une aire privée de données de taille
private_data_size
donné en argument à la callback lors
de son exécution.
Voici un exemple de fonction callback pour
InjectionPointCallback
:
static void custom_injection_callback(const char *name, const void *private_data) { uint32 wait_event_info = WaitEventInjectionPointNew(name); pgstat_report_wait_start(wait_event_info); elog(NOTICE, "%s: executed custom callback", name); pgstat_report_wait_end(); }
Cette fonction callback affiche un message dans les traces du serveur de
niveau NOTICE
, mais les fonctions callbacks peuvent
implémenter une logique bien plus complexe.
En option, il est possible de détacher un point d'injection en appelant :
extern bool InjectionPointDetach(const char *name);
En cas de succès, true
est renvoyé,
false
sinon.
Une fonction callback attachée à un point d'injection est disponible pour
tous les processus serveur, y compris ceux démarrés après l'appel à
InjectionPointAttach
. Elle reste attachée pendant toute
l'exécution du serveur, à moins qu'elle ne soit détachée en utilisant
InjectionPointDetach
.
Un exemple est disponible dans
src/test/modules/injection_points
du répertoire des
sources de PostgreSQL.
Activer les points d'injection requiert l'utilisation de l'option
--enable-injection-points
avec
configure
ou -Dinjection_points=true
avec Meson.
Bien que le moteur PostgreSQL soit écrit en C, il est possible de coder des extensions en C++ si les lignes de conduite suivantes sont respectées :
Toutes les fonctions accessibles par le serveur doivent
présenter une interface en C ;
seules ces fonctions C pourront alors appeler du code C++.
Ainsi, l'édition de liens extern C
est nécessaire
pour les fonctions appelées par le serveur. Ceci est également obligatoire
pour toutes les fonctions passées comme pointeur entre le serveur et du
code C++.
Libérez la mémoire en utilisant la méthode de désallocation appropriée.
Par exemple, la majeure partie de la mémoire allouée par le serveur l'est
par appel de la fonction palloc()
, aussi, il convient
de libérer ces zones mémoire en utilisant la fonction pfree()
.
L'utilisation de la fonction C++ delete
échouerait
pour ces blocs de mémoire.
Évitez la propagation d'exceptions dans le code C (utilisez un bloc
catch-all au niveau le plus haut de toute fonction extern C
.
Ceci est nécessaire, même si le code C++ n'émet explicitement aucune
exception, dans la mesure où la survenue d'événements tels qu'un manque de mémoire
peut toujours lancer une exception. Toutes les exceptions devront être gérées
et les erreurs correspondantes transmises via l'interface du code C.
Si possible, compilez le code C++ avec l'option -fno-exceptions
afin d'éliminer entièrement la venue d'exceptions ; dans ce cas, vous
devrez effectuer vous-même les vérifications correspondantes dans votre code C++,
par exemple, vérifier les éventuels paramètres NULL retournés par la fonction
new()
.
Si vous appelez des fonctions du serveur depuis du code C++, assurez vous que
la pile d'appels ne contienne que des structures C (POD).
Ceci est nécessaire dans la mesure où les erreurs au niveau du serveur génèrent un
saut via l'instruction longjmp()
qui ne peut dépiler proprement
une pile d'appels C++ comportant des objets non-POD.
Pour résumer, le code C++ doit donc être placé derrière un rempart de fonctions
extern C
qui fourniront l'interface avec le serveur, et devra éviter
toute fuite de mécanismes propres au C++ (exceptions, allocation/libération de mémoire
et objets non-POD dans la pile).