PostgreSQLLa base de données la plus sophistiquée au monde.

33.9. Fonctions en langage C

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é.

33.9.1. Chargement dynamique

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é :

  1. Si le nom est un chemin absolu, le fichier est chargé.

  2. 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.

  3. 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.

  4. 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.

[Note]

Note

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 33.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 compilet 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.)

33.9.2. Types de base dans les fonctions en langage C

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;

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 et 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.

[Avertissement]

Avertissement

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 33.10, « Agrégats utilisateur ».

Comme exemple, nous pouvons définir le type text comme ceci :

typedef struct {
    int4 length;
    char data[1];
} text;

Il est évident que le champ déclaré ici n'est pas assez long pour contenir toutes les chaînes possibles. Comme il est impossible de déclarer une structure de taille variable en C, nous nous appuyons sur le fait que le compilateur C ne vérifie pas la plage des indices de tableau. Nous allouons juste la quantité d'espace nécessaire et ensuite nous accédons au tableau comme s'il avait été déclaré avec la bonne longueur (c'est une astuce courante que vous pouvez trouver dans beaucoup de manuels de C).

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);
destination->length = VARHDRSZ + 40;
memcpy(destination->data, buffer, 40);
...

VARHDRSZ est équivalent à sizeof(int4) mais est considéré comme une meilleure tournure de référence à la taille de l'overhead pour un type à longueur variable.

Le Tableau 33.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 33.1. Équivalence des types C et des types SQL intégrés

Type SQL Type C Défini dans
abstime AbsoluteTime utils/nabstime.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) int2 or int16 postgres.h
int2vector int2vector* postgres.h
integer (int4) int4 or int32 postgres.h
real (float4) float4* postgres.h
double precision (float8) float8* postgres.h
interval Interval* utils/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* utils/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.

33.9.3. Conventions d'appel de la version 0

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>

/* 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));
    VARATT_SIZEP(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);

    VARATT_SIZEP(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.

33.9.4. Conventions d'appel de la version 1

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"

/* 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));
    VARATT_SIZEP(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);

    VARATT_SIZEP(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 33.9.10, « Renvoi d'ensembles »), l'implémentation de fonctions déclencheurs (Chapitre 34, Déclencheurs (triggers)) et d'opérateurs d'appel de langage procédural (Chapitre 47, É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.

33.9.5. Écriture du code

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 33.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 to définir un « bloc magique » pour votre bibliothèque partagée, comme décrit dans Section 33.9.1, « Chargement dynamique ».

  • Quand vous allouez de la mémoire, utilisez les fonctions PostgreSQLpalloc 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. Sinon, il est difficile de supporter les index ou les jointures de découpage car vous devez retenir seulement les bits significatifs de votre structure de donnée pour calculer un découpage. Même si vous initialisez tous les champs de votre structure, il peut y avoir des remplissages d'alignement (trous dans la structure) pouvant contenir des valeurs parasites.

  • 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.

33.9.6. Compiler et lier des fonctions chargées dynamiquement

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 les informations au-delà de ce qui est abordé 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îne 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.

BSD/OS

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
ld -shared -o foo.so foo.o

Ceci est applicable à partir de la version 4.0 de BSD/OS.

FreeBSD

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.

HP-UX

L'option du compilateur du système pour créer des PIC est +z. Avev 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.

IRIX

PIC est l'option par défaut. Aucune option de compilation particulière n'est nécessaire. Le commutateur de l'éditeur de liens pour produire des bibliothèques partagées est -shared.

cc -c foo.c
ld -shared -o foo.so foo.o
Linux

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
MacOS X

Voici un exemple. Il 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
NetBSD

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
OpenBSD

L'option du compilateur pour créer des PIC est -fpic. Les bibliothèques partagées peuvent être créées avec la commande suivante ld -Bshareable.

gcc -fpic -c foo.c
ld -Bshareable -o foo.so foo.o
Solaris

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
Tru64 UNIX

Par défaut, il s'agit de PIC. Ainsi, aucune directive particulière n'est à fournir pour la compilation. Pour l'édition de lien, des options spécifiques sont à fournir à ld :

cc -c foo.c
ld -shared -expect_unresolved '*' -o foo.so foo.o

Une procédure identique doit être employée dans le cas où GCC est utilisé à la place du compilateur du système ; aucune option particulière n'est nécessaire.

UnixWare

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
[Astuce]

Astuce

Si cela s'évè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 33.9.1, « Chargement dynamique » indique l'endroit où le serveur s'attend à trouver les fichiers de bibliothèques partagées.

33.9.7. Infrastructure de construction d'extensions

Si vous pensez distribuer vos modules d'extension PostgreSQL™, configurer un système de construction portable peut être assez compliqué. Du coup, l'installation de PostgreSQL™ fournit une infrastructure de construction pour les extensions, appelée PGXS, pour que les modules d'extension simples puissent être construit simplement avec un serveur déjà installé. Notez que cette infrastructure n'a pas pour but d'être un ensemble de travail universel pouvant être utilisé pour construire tous les logiciels s'interfaçant avec PostgreSQL™ ; il automatise simplement les règles de construction communes pour les modules simples d'extension du serveur. Pour des paquetages plus complexes, vous aurez besoin d'écrire votre propre système de construction.

Pour utiliser l'infrastructure de votre extension, vous devez écrire un simple fichier makefile. Dans ce fichier, vous devez configurer quelques variables et inclure enfin le makefile global PGXS. Voici un exemple qui construit un module d'extension nommé isbn_issn consistant en une bibliothèque partagée, un script SQL et un fichier texte de documentation :

MODULES = isbn_issn
DATA_built = isbn_issn.sql
DOCS = README.isbn_issn
    
PGXS := $(shell pg_config --pgxs)
include $(PGXS)

Les deux dernières lignes devraient toujours être identiques. Plus tôt dans le fichier, vous affectez des variables ou ajoutez vos propres règles pour make.

Les variables suivantes peuvent être configurées :

MODULES

liste des objets partagés à construire à partir du fichier source de même base (ne pas inclure le suffixe dans cette liste)

DATA

fichiers spécifiques à installer dans prefix/share/contrib

DATA_built

fichiers spécifiques à installer dans prefix/share/contrib qui ont besoin d'être construit au début

DOCS

fichiers spécifiques à installer dans prefix/doc/contrib

SCRIPTS

fichiers script (pas des binaires) à installer dans prefix/bin

SCRIPTS_built

fichiers script (pas des binaires) à installer dans prefix/bin qui ont besoin d'être construit au début

REGRESS

liste des cas de tests de regression (sans suffixe), voir ci-dessous

ou au moins un parmi ces deux-là :

PROGRAM

un programme binaire à construire (liste des fichiers objets dans OBJS)

MODULE_big

un objet partagé à construire (liste des fichiers objets dans OBJS)

Ce qui suit peut être configuré :

EXTRA_CLEAN

fichiers supplémentaires à supprimer dans make clean

PG_CPPFLAGS

sera ajouté à CPPFLAGS

PG_LIBS

sera ajouté à la ligne de liens PROGRAM

SHLIB_LINK

sera ajouté à la ligne de lien MODULE_big

Nommez ce fichier Makefile et placez le dans le répertoire qui contient votre extension. Ensuite, vous pouvez lancer la compilation avec make, et plus tard make install pour installer votre module. L'extension est compilée et installée pour l'installation de PostgreSQL™ qui correspond à la première commande pg_config trouvée dans votre chemin.

Les scripts listés dans la variable REGRESS sont utilisés pour les tests de régression de votre module, tout comme make installcheck est utilisé pour votre serveur PostgreSQL™. Pour que ceci fonctionne, vous avez besoin d'avoir un sous-répertoire nommé sql/ dans le répertoire de votre extension, à l'intérieur duquel vous placez un fichier pour chaque groupe de tests que vous voulez exécuter. Les fichiers doivent avoir l'extension .sql, qui ne seront pas inclus dans la liste REGRESS dans le Makefile. Pour chaque test, il doit exister un fichier contenant le résultat attendu dans un sous-répertoire nommé expected/, avec l'extension .out. Les tests sont exécutés via make installcheck, et le résultat sera comparé aux fichiers attendus. Les différences seront écrites dans le fichier regression.diffs dans un format diff -c. Notez qu'essayer d'exécuter un test à qui il manque le fichier sera rapporté comme un « problème », donc assurez-vous que vous avez bien tous les fichiers attendus.

[Astuce]

Astuce

La façon la plus simple de créer les fichiers attendus est de créer des fichiers vides, puis de vérifier avec précaution les fichiers résultants après un test d'exécution (à trouver dans un répertoire results/), et les copier dans expected/ s'ils correspondent à votre souhait suite aux tests.

33.9.8. Arguments de type composite

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() */

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() */

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.

33.9.9. Renvoi de lignes (types composites)

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 »).

[Astuce]

Astuce

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.

[Note]

Note

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 pour 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.

33.9.10. Renvoi d'ensembles

Il existe aussi une API spéciale procurant le moyen de renvoyer des ensembles (lignes multiples) depuis une fonction en langage C. Une fonction renvoyant un ensemble doit suivre les conventions d'appel de la version-1. Aussi, les fichiers source doivent inclure l'en-tête funcapi.h, comme ci-dessus.

Une fonction renvoyant un ensemble (SRF : « set returning function ») est appelée une fois pour chaque élément qu'elle renvoie. La SRF doit donc sauvegarder suffisamment l'état pour se rappeler ce qu'elle était en train de faire et renvoyer le prochain élément à chaque appel. La structure FuncCallContext est offerte pour assister le contrôle de ce processus. À l'intérieur d'une fonction, fcinfo->flinfo->fn_extra est utilisée pour conserver un pointeur vers FuncCallContext au cours des appels successifs.

typedef struct
{
    /*
     * 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.
     */
    uint32 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.
     */
    uint32 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;

Une SRF utilise plusieurs fonctions et macros qui manipulent automatiquement la structure FuncCallContext (et s'attendent à la trouver via fn_extra). Utilisez

SRF_IS_FIRSTCALL()

pour déterminer si votre fonction est appelée pour la première fois. Au premier appel, utilisez

SRF_FIRSTCALL_INIT()

pour initialiser la structure FuncCallContext. À chaque appel de fonction, y compris le premier, utilisez

SRF_PERCALL_SETUP()

pour une mise à jour correcte en vue de l'utilisation de FuncCallContext et pour nettoyer toutes les données renvoyées précédemment et conservées depuis le dernier passage de la fonction.

Si votre fonction a des données à renvoyer, 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.

Voici un exemple complet de pseudo-code :

Datum
my_set_returning_function(PG_FUNCTION_ARGS)
{
    FuncCallContext  *funcctx;
    Datum             result;
    MemoryContext     oldcontext;
    further declarations as needed

    if (SRF_IS_FIRSTCALL())
    {
        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 and just need to clean up: */
        user code
        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.

Le répertoire contrib/tablefunc situé dans les fichiers source de la distribution contient d'autres exemples de fonctions renvoyant des ensembles.

33.9.11. Arguments polymorphes et types renvoyés

Les fonctions en langage C peuvent être déclarées pour accepter et renvoyer les types « polymorphes » anyelement et anyarray. Voir la Section 33.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.

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.

33.9.12. Mémoire partagée et LWLocks

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 RequestAddinLWLocks(int n)

à partir de _PG_init.

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 (!ptr)
                        elog(ERROR, "out of shared memory");
                if (!found)
                {
                        initialize contents of shmem area;
                        acquire any requested LWLocks using:
                        ptr->mylockid = LWLockAssign();
                }
                LWLockRelease(AddinShmemInitLock);
        }