PostgreSQL: Développement d'extensions (fonctions) en langage C

Cet article a été écrit il y a quelques années et ne savait pas où il pouvait être placé, puis il a oublié.

La signification de l'utilisation du langage C lors du développement d'extensions pour PostgreSQL par rapport aux langages interprétés (scriptés) peut être réduite à deux points: performances et fonctionnalités. Mais simplement, le code écrit en C fonctionnera beaucoup plus rapidement, par exemple, si la fonction est appelée un million de fois dans la demande pour chaque enregistrement. Plus spécifiquement, certaines fonctionnalités de PostgreSQL ne peuvent pas être effectuées du tout sauf en C, par exemple, dans d'autres langages, les types (surtout si vous retournez une valeur à partir d'une fonction) ANYELEMENT , ANYARRAY et particulièrement important VARIADIC ne sont pas pris en charge .

Fonction C simple


Par exemple, nous écrivons une fonction qui prend deux arguments et les ajoute. Cet exemple est décrit dans la documentation de PostgreSQL, mais nous allons l'améliorer légèrement et le collecter. Chargez ensuite dans PostgreSQL et écrivez un appel à partir de la fonction SQL.

#include "postgres.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(add_ab);
Datum add_ab(PG_FUNCTION_ARGS)
{
    int32 arg_a = PG_GETARG_INT32(0);
    int32 arg_b = PG_GETARG_INT32(1);
    PG_RETURN_INT32(arg_a + arg_b);
}

Le fichier add_func.c peut être utilisé comme modèle pour développer des fonctionnalités plus complexes. Passez également en revue le code

  • #include "postgresql.h": ce fichier d'en-tête que vous devrez toujours inclure, contient divers types et fonctions de base.
  • #include "fmgr.h": le fichier d'en-tête contient diverses macros PG_ *.
  • PG_MODULE_MAGIC: une macro qui détermine que nous développons un module pour PostgreSQL supérieur à la version 8.2.
  • PG_FUNC_INFO_V1: Une macro définissant les conventions d'appel de fonction dans PostgreSQL. Si vous déclarez une fonction sans elle, il y aura un accord sur l'appel de la version 0, sinon la version 1.
  • Datum: . , PostgreSQL. - VARIANT Microsoft. «» - . , .
  • add_ab(PG_FUNCTION_ARGS): . - . , .
  • int32 arg_a = PG_GETARG_INT32(0): ( ).
  • PG_RETURN_INT32(arg_a + arg_b): .

Maintenant, nous devons compiler et compiler cela correctement. La sortie sera une bibliothèque partagée chargeable dynamiquement (* .so). Pour cela, il sera plus pratique de le faire via le Makefile . La documentation décrit les clés et les chemins qui doivent être enregistrés, mais nous collecterons à l'aide de PGXS . Il s'agit d'un environnement pour les développeurs d'extensions, ce qui signifie que tous les packages -dev et -devel nécessaires pour PostgreSQL doivent être installés sur votre système .

MODULES = add_func
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

Nous créons la fonction SQL


Nous allons maintenant écrire une fonction SQL qui sera appelée à partir de notre bibliothèque d'extensions créée précédemment.
CREATE FUNCTION add(int, int)
  RETURNS int
AS '/usr/lib/postgresql/9.4/lib/add_func', 'add_ab'
LANGUAGE C STRICT;

C'est tout! Maintenant, nous pouvons utiliser cette fonction pour

SELECT add(1, 2);

Automatisez l'installation


Maintenant, nous automatisons un peu l'installation. Cela sera très utile lorsque vous ne saurez pas à l'avance quelle version de PostgreSQL est utilisée et de quelle manière elle est installée. Pour ce faire, créez le fichier suivant,

CREATE FUNCTION add(int, int)
  RETURNS int
AS 'MODULE_PATHNAME', 'add_ab'
LANGUAGE C STRICT;

et ajoutez une ligne au Makefile ,

MODULES = add_func
DATA_built = add_funcs.sql
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

Améliorez un peu


Nous allons apporter quelques modifications à notre exemple et apporter quelques améliorations.

Vérification des arguments


Rappelez-vous que dans la définition SQL de la fonction add (int, int) , nous avons utilisé le mot clé STRICT ? Cela signifie que si au moins l'un des arguments est NULL , la fonction ne fonctionnera pas et renverra simplement NULL . Ceci est similaire au comportement des instructions SQL, par exemple, s'il y a au moins un argument NULL dans l'opérateur + , le résultat sera NULL . Nous ajoutons une vérification d'argument à notre fonction, similaire à la fonction d'agrégation SQL sum () , qui ignore les valeurs NULL et continue de fonctionner. Pour cela, nous devons faire



  • Assurez-vous que la fonction fonctionne même si l'un des arguments est NULL
  • Si les deux arguments sont NULL , retournez NULL

PG_FUNCTION_INFO_V1(add_ab_null);
Datum add_ab_null(PG_FUNCTION_ARGS)
{
    int32 not_null = 0;
    int32 sum = 0;
    if (!PG_ARGISNULL(0))
    {
        sum += PG_GETARG_INT32(0);
        not_null = 1;
    }
    if (!PG_ARGISNULL(1))
    {
        sum += PG_GETARG_INT32(1);
        not_null = 1;
    }
    if (not_null)
    {
        PG_RETURN_INT32(sum);
    }
    PG_RETURN_NULL();
}

Et maintenant, vérifiez-le,

CREATE FUNCTION add(int, int)
 RETURNS int
AS '$libdir/add_func', 'add_ab_null'
LANGUAGE C;

SELECT add(NULL, NULL) AS must_be_null, add(NULL, 1) AS must_be_one;
-[ RECORD 1 ]+--
must_be_null |
must_be_one  | 1

Et voici comment obtenir la même chose avec les outils PostgreSQL standard,
SELECT (CASE WHEN (a IS null) AND (b IS null)
(THEN null
ELSE coalesce(a, 0) + coalesce(b,0)
END)
FROM (SELECT 1::int AS a, null::int AS b) s;
-[ RECORD 1 ]
 case | 1

N'importe quel nombre d'arguments dans une fonction


Comme vous l'avez déjà remarqué, nous avons utilisé des macros pour obtenir la valeur des arguments. Par conséquent, nous pouvons passer n'importe quel nombre d'arguments, puis simplement lire leurs valeurs dans une boucle,

if (!PG_ARGISNULL(i))
{
    sum += PG_GETARG_INT32(i);
    not_null = 1;
}

Vous pouvez avoir immédiatement une question, mais vous pouvez utiliser un tableau pour passer la valeur des arguments. Et en fait, c'est ce qui doit être fait, mais malheureusement en raison de la présence de son propre gestionnaire de mémoire dans PostgreSQL, ce n'est pas une tâche si banale. Mais essayez de le résoudre. Il y a un exemple dans la documentation PostgreSQL où le tableau de caractères text [] est passé, mais ce n'est pas exactement ce dont nous avons besoin. Essayons de nous adapter à nos besoins,

#include "utils/array.h"     //       
#include "catalog/pg_type.h" // INT4OID
PG_MODULE_MAGIC;
Datum add_int32_array(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(add_int32_array);
Datum add_int32_array(PG_FUNCTION_ARGS)
{
    //     .
    //          ,       int.
    ArrayType *input_array;
    int32 sum = 0;
    bool not_null = false;

    Datum *datums;
    bool *nulls;
    int count;
    int i;
    input_array = PG_GETARG_ARRAYTYPE_P(0); //     .  *_P  
                                            //        ,   INT32

    //          INT32 (INT4)
    Assert(ARR_ELEMTYPE(input_array) == INT4OID);

    //      
    if (ARR_NDIM(input_array) > 1)
        ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("1-dimensional array needed")));

    deconstruct_array(input_array, //  
                      INT4OID,     //  
                      4,           //    
                      true,        // int4   
                      'i',         //  'i'
                      &datums, &nulls, &count); //   

    for(i = 0; i < count; i++)
    {
        //     
        if (nulls[i])
            continue;

        // ,       
        sum += DatumGetInt32(datums[i]);
        not_null = true;
    }
    if (not_null)
        PG_RETURN_INT32(sum);

    PG_RETURN_NULL();
}

Comme toujours, passons en revue le code,

  • Il n'y a pas de type spécial pour les tableaux entiers, nous utilisons donc le type générique ArrayType , qui fonctionne pour tout type de tableau
  • Pour initialiser le tableau avec le premier argument, nous avons utilisé la macro spéciale PG_GETARG_ARRAYTYPE_P
  • Il y a aussi une vérification pour voir si le tableau est vraiment ARR_NDIM unidimensionnel
  • Le type OID pour int4 (= 23) est défini comme INT4OID. Pour afficher les définitions d'autres types, vous pouvez utiliser SQL,

    select oid, typlen, typbyval, typalign from pg_type
    where typname = 'int4';
    -[ RECORD 1 ]
    oid | 23
    typlen | 4
    typbyval | t
    typalign | i
    


Il ne nous reste plus qu'à apprendre à PostgreSQL à l'utiliser en déclarant une fonction qui prend un argument int [],

CREATE OR REPLACE FUNCTION add_arr(int[]) RETURNS int
AS '$libdir/add_func', 'add_int32_array'
LANGUAGE C STRICT;

Et vérifie

SELECT add_arr('{1,2,3,4,5,6,7,8,9}');
-[ RECORD 1 ]
add_arr | 45
SELECT add_arr(ARRAY[1,2,NULL]);
-[ RECORD 1 ]
add_arr | 3
SELECT add_arr(ARRAY[NULL::int]);
-[ RECORD 1 ]
add_arr |

Recommandations


Résumons maintenant certaines des principales recommandations pour la création d'extensions.

Travailler avec la mémoire


Les auteurs et créateurs du SGBD PostgreSQL accordent une attention particulière au travail avec la mémoire et à la prévention des fuites. C'est l'une des règles de base pour développer un SGBD et ses extensions. Ceci est basé sur leur propre décision - le contexte de la mémoire.

Utilisation de palloc () et pfree ()


Tout le travail avec la mémoire dans PostgreSQL consiste à utiliser les fonctions non standard palloc () et pfree ().

Initialisation des structures


Toujours initialiser les nouvelles structures déclarées. Par exemple, appelez memset () après palloc ().

Fichiers connectés


Chacune de vos extensions doit inclure au moins deux fichiers: postgres.h et fmgr.h.

Liens utiles


PostgreSQL: Interface de programmation serveur .

PostgreSQL: fonctions définies par l'utilisateur dans le réseau d'extension C

PostgreSQL

All Articles