Documentation PostgreSQL 9.6.24 > Programmation serveur > Étendre SQL > Fonctions en langage C | |
Fonctions internes | Agrégats utilisateur |
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).
Deux différentes conventions d'appel sont actuellement en usage pour les fonctions C. La plus récente, « version 1 », est indiquée en écrivant une macro d'appel PG_FUNCTION_INFO_V1() comme illustré ci-après. L'absence d'une telle macro indique une fonction écrite selon l'ancien style (« version 0 »). Le nom de langage spécifié dans la commande CREATE FUNCTION est C dans les deux cas. Les fonctions suivant l'ancien style sont maintenant déconseillées en raison de problèmes de portabilité et d'un manque de fonctionnalité mais elles sont encore supportées pour des raisons de compatibilité.
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.9.6, « Compiler et lier des fonctions chargées dynamiquement » 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™. Un bloc magique est requis à partir de PostgreSQL™ 8.2. 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 :
#ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif
Le test #ifdef peut être omis si le code n'a pas besoin d'être compilé avec des versions de PostgreSQL™ antérieures à la 8.2.
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 des fonctions d'initialisation et de terminaison. 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. Si le fichier inclut une fonction nommée _PG_fini, cette fonction sera appelée tout juste avant le déchargement du fichier. De la même façon, la fonction ne reçoit aucun paramètre et doit renvoyer void. Notez que _PG_fini sera seulement appelée lors du déchargement du fichier, pas au moment de la fin du processus. (Actuellement, les déchargements sont désactivés et ne surviendront jamais, bien que cela puisse changer un jour.)
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 intXX signifie 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.1, « Équivalence des types C et des types SQL intégrés ».)
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.10, « Agrégats utilisateur ».
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. Also, the length field must be set using the SET_VARSIZE macro, not by simple assignment.
Le Tableau 36.1, « Équivalence des types C et des types SQL intégrés » spécifie la correspondance entre les types C et les types SQL quand on écrit une fonction en langage C utilisant les types internes 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 car il déclare un grand nombre d'éléments dont vous aurez besoin de toute façon.
Tableau 36.1. Équivalence des types C et des types SQL intégrés
Type SQL | Type C | Défini dans |
---|---|---|
abstime | AbsoluteTime | utils/nabstime.h |
bigint (int8) | int64 | postgres.h |
boolean | bool | postgres.h (intégration au compilateur) |
box | BOX* | utils/geo_decls.h |
bytea | bytea* | postgres.h |
"char" | char | (intégré au compilateur) |
character | BpChar* | postgres.h |
cid | CommandId | postgres.h |
date | DateADT | utils/date.h |
smallint (int2) | int16 | postgres.h |
int2vector | int2vector* | postgres.h |
integer (int4) | int32 | postgres.h |
real (float4) | float4* | postgres.h |
double precision (float8) | float8* | postgres.h |
interval | Interval* | datatype/timestamp.h |
lseg | LSEG* | utils/geo_decls.h |
name | Name | postgres.h |
oid | Oid | postgres.h |
oidvector | oidvector* | postgres.h |
path | PATH* | utils/geo_decls.h |
point | POINT* | utils/geo_decls.h |
regproc | regproc | postgres.h |
reltime | RelativeTime | utils/nabstime.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 |
tinterval | TimeInterval | utils/nabstime.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.
Nous présentons l'« ancien style » de convention d'appel en premier -- bien que cette approche soit maintenant déconseillée, elle est plus facile à maîtriser au début. Dans la méthode version-0, les arguments et résultats de la fonction C sont simplement déclarés dans le style C normal mais en faisant attention à utiliser la représentation C de chaque type de données SQL comme montré ci-dessus.
Voici quelques exemples :
#include "postgres.h" #include <string.h> #include "utils/geo_decls.h" #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif /* par valeur */ int add_one(int arg) { return arg + 1; } /* par référence, taille fixe */ float8 * add_one_float8(float8 *arg) { float8 *result = (float8 *) palloc(sizeof(float8)); *result = *arg + 1.0; return result; } Point * makepoint(Point *pointx, Point *pointy) { Point *new_point = (Point *) palloc(sizeof(Point)); new_point->x = pointx->x; new_point->y = pointy->y; return new_point; } /* par référence, taille variable */ text * copytext(text *t) { /* * VARSIZE est la taille totale de la structure en octets. */ text *new_t = (text *) palloc(VARSIZE(t)); SET_VARSIZE(new_t, VARSIZE(t)); /* * VARDATA est un pointeur sur la région de données de la structure. */ memcpy((void *) VARDATA(new_t), /* destination */ (void *) VARDATA(t), /* source */ VARSIZE(t) - VARHDRSZ); /* nombre d'octets */ return new_t; } text * concat_text(text *arg1, text *arg2) { int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ; text *new_text = (text *) palloc(new_text_size); SET_VARSIZE(new_text, new_text_size); memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1) - VARHDRSZ); memcpy(VARDATA(new_text) + (VARSIZE(arg1) - VARHDRSZ), VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ); return new_text; }
En supposant que le code ci-dessus ait été écrit dans le fichier funcs.c et compilé en objet partagé, nous pourrions définir les fonctions pour PostgreSQL™ avec des commandes comme ceci :
CREATE FUNCTION add_one(integer) RETURNS integer AS 'DIRECTORY/funcs', 'add_one' LANGUAGE C STRICT; -- notez la surcharge du nom de la fonction SQL "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 représente le répertoire contenant le fichier de la bibliothèque partagée (par exemple le répertoire du tutoriel de PostgreSQL™, qui contient le code des exemples utilisés dans cette section). (Un meilleur style aurait été d'écrire seulement 'funcs' dans la clause AS, après avoir ajouté DIRECTORY au chemin de recherche. Dans tous les cas, nous pouvons omettre l'extension spécifique au système pour les bibliothèques partagées, communément .so ou .sl.)
Remarquez que nous avons spécifié la fonction comme « STRICT », ce qui signifie que le système devra automatiquement supposer un résultat NULL si n'importe quelle valeur d'entrée est NULL. Ainsi, nous évitons d'avoir à vérifier l'existence d'entrées NULL dans le code de la fonction. Sinon, nous aurions dû contrôler explicitement les valeurs NULL en testant un pointeur NULL pour chaque argument passé par référence (pour les arguments passés par valeur, nous n'aurions même aucun moyen de contrôle !).
Bien que cette convention d'appel soit simple à utiliser, elle n'est pas très portable ; sur certaines architectures, il y a des problèmes pour passer de cette manière des types de données plus petits que int. De plus, il n'y a pas de moyen simple de renvoyer un résultat NULL, ni de traiter des arguments NULL autrement qu'en rendant la fonction strict. La convention version-1, présentée ci-après, permet de surmonter ces objections.
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_xxx() correspondant au type de donnée de l'argument et le résultat est renvoyé par une macro PG_RETURN_xxx() correspondant au type renvoyé. PG_GETARG_xxx() prend comme argument le nombre d'arguments de la fonction à parcourir, le compteur commençant à 0. PG_RETURN_xxx() prend comme argument la valeur effective à renvoyer.
Voici la même fonction que précédemment, codée en style version-1
#include "postgres.h" #include <string.h> #include "fmgr.h" #include "utils/geo_decls.h" #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif /* 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_P(0); /* * VARSIZE est la longueur totale de la structure en octets. */ text *new_t = (text *) palloc(VARSIZE(t)); SET_VARSIZE(new_t, VARSIZE(t)); /* * VARDATA est un pointeur vers la région de données de la structure. */ memcpy((void *) VARDATA(new_t), /* destination */ (void *) VARDATA(t), /* source */ VARSIZE(t) - VARHDRSZ); /* nombre d'octets */ PG_RETURN_TEXT_P(new_t); } PG_FUNCTION_INFO_V1(concat_text); Datum concat_text(PG_FUNCTION_ARGS) { text *arg1 = PG_GETARG_TEXT_P(0); text *arg2 = PG_GETARG_TEXT_P(1); int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ; text *new_text = (text *) palloc(new_text_size); SET_VARSIZE(new_text, new_text_size); memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1) - VARHDRSZ); memcpy(VARDATA(new_text) + (VARSIZE(arg1) - VARHDRSZ), VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ); PG_RETURN_TEXT_P(new_text); }
Les commandes CREATE FUNCTION sont les mêmes que pour leurs équivalents dans la version-0.
Au premier coup d'œil, les conventions de codage de la version-1 peuvent sembler inutilement obscures. Pourtant, elles offrent nombre d'améliorations car les macros peuvent cacher les détails superflus. Un exemple est donné par la fonction add_one_float8 où nous n'avons plus besoin de prêter attention au fait que le type float8 est passé par référence. Un autre exemple de simplification est donné par les macros pour les types à longueur variable GETARG qui permettent un traitement plus efficace des valeurs « toasted » (compressées ou hors-ligne).
Une des grandes améliorations dans les fonctions version-1 est le meilleur traitement des entrées et des résultats NULL. La macro PG_ARGISNULL(n) 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 PG_GETARG_xxx(), les arguments en entrée sont comptés à partir de zéro. Notez qu'on doit se garder d'exécuter PG_GETARG_xxx() jusqu'à ce qu'on ait vérifié que l'argument n'est pas NULL. Pour renvoyer un résultat NULL, exécutez la fonction PG_RETURN_NULL() ; ceci convient aussi bien dans les fonctions STRICT que non STRICT.
Les autres options proposées dans l'interface de nouveau style sont deux variantes des macros PG_GETARG_xxx(). La première d'entre elles, PG_GETARG_xxx_COPY(), 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 PG_GETARG_xxx_COPY(), on garantit l'écriture du résultat). La seconde variante se compose des macros PG_GETARG_xxx_SLICE() 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 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.9.9, « Renvoi d'ensembles »), l'implémentation de fonctions déclencheurs (Chapitre 37, Déclencheurs (triggers)) et d'opérateurs d'appel de langage procédural (Chapitre 54, Écrire un gestionnaire de langage procédural). Le code version-1 est aussi plus portable que celui de version-0 car il ne contrevient pas aux restrictions du protocole d'appel de fonction en C standard. 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.9.6, « Compiler et lier des fonctions chargées dynamiquement » 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.9.1, « Chargement dynamique ».
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.
gcc -fPIC -c foo.c gcc -shared -o foo.so foo.o
Ceci est applicable à partir de la version 3.0 de FreeBSD.
L'option du compilateur du système pour créer des PIC est +z. Avec GCC, l'option est -fPIC. Le commutateur de l'éditeur de liens pour les bibliothèques partagées est -b. Ainsi :
cc +z -c foo.c
ou :
gcc -fPIC -c foo.c
puis :
ld -b -o foo.sl foo.o
HP-UX utilise l'extension .sl pour les bibliothèques partagées, à la différence de la plupart des autres systèmes.
L'option du compilateur pour créer des PIC est -fPIC. Sur certaines plateformes et dans certaines situations, -fPIC doit être utilisé si -fPIC ne fonctionne pas. Le manuel de GCC donne plus d'informations. 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
L'option de compilation pour créer des PIC est -KPIC avec le compilateur SCO et -fPIC avec GCC™. Pour lier des bibliothèques partagées, les options respectives sont -G et -shared.
cc -K PIC -c foo.c cc -G -o foo.so foo.o
ou
gcc -fPIC -c foo.c gcc -shared -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.9.1, « Chargement dynamique » 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 0, nous pouvons définir c_surpaye comme :
#include "postgres.h" #include "executor/executor.h" /* pour GetAttributeByName() */ #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif bool c_surpaye(HeapTupleHeader *t, /* la ligne courante d'emp */ int32 limite) { bool isNULL; int32 salaire; salaire = DatumGetInt32(GetAttributeByName(t, "salaire", &isNULL)); if (isNULL) return false; return salaire > limite; }
Dans le codage version-1, le code ci-dessus devient :
#include "postgres.h" #include "executor/executor.h" /* pour GetAttributeByName() */ #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif 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 macro appropriée DatumGetXXX(). 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.
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 result slot * * This is obsolete and only present for backwards compatibility, viz, * user-defined SRFs that use the deprecated TupleDescGetSlot(). */ TupleTableSlot *slot; /* * 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_xxx) 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 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_UINT32(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 » anyelement, anyarray, anynonarray, anyenum et anyrange. Voir la Section 36.2.5, « Types et fonctions polymorphes » pour une explication plus détaillée des fonctions polymorphes. 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 utiledans 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é avec VARIADIC.
Certains appels de fonctions pourraient être simplifiés lors de la planification en se basant sur les propriétés spécifiques de la fonction. Par exemple, int4mul(n, 1) pourrait être simplifié par n. Pour définir des tels optimisations spécifiques aux fonctions, écrivez une fonction de transformation et placez son OID dans le champ protransform de l'entrée pg_proc de la fonction principale. La fonction de transformation doit avoir la signature SQL suivante : protransform(internal) RETURNS internal. L'argument, actuellement un FuncExpr *, est un nœud vide représentant un appel à la fonction principale. Si l'étude de la fonction de transformation sur l'arbre d'expression prouve qu'un arbre d'expression simplifié peut être substitué pour tous les appels réels effectués après, elle construit et renvoie l'expression simplifiée. Sinon, elle renvoie un pointeur NULL (pas un NULL SQL).
Nous ne donnons aucune garantie que PostgreSQL™ n'appelera jamais la fonction principale dans les cas que la fonction de transformation pourrait simplifier. Assurez-vous d'une équivalent rigoureuse entre l'expression simplifiée et un appel réel de la fonction principale.
Actuellement, cette fonctionnalité n'est pas offerte aux utilisateurs via le niveau SQL à cause des risques de sécurité. C'est donc uniquement utilisé pour optimiser les fonctions internes.
Les modules peuvent réserver des LWLocks et allouer de la mémoire partagée au lancement du serveur. La bibliothèque partagée du module doit être préchargée en l'ajoutant shared_preload_libraries. La mémoire partagée est réservée en appelant :
void RequestAddinShmemSpace(int size)
à partir de votre fonction _PG_init.
Les LWLocks sont réservés en appelant :
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
à partir de _PG_init. Ceci assurera qu'un tableau de num_lwlocks LWLocks est disponible sous le nom de tranche_name. Utilisez GetNamedLWLockTranche pour obtenir un pointeur vers ce tableau.
Pour éviter des cas rares possibles, chaque moteur devrait utiliser la fonction AddinShmemInitLock lors de la connexion et de l'initialisation de la mémoire partagée, comme indiquée ci-dessous :
static mystruct *ptr = NULL; if (!ptr) { bool found; LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); ptr = ShmemInitStruct("my struct name", size, &found); if (!found) { initialize contents of shmem area; acquire any requested LWLocks using: ptr->locks = GetNamedLWLockTranche("my tranche name"); } LWLockRelease(AddinShmemInitLock); }
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).