PostgreSQL: Desarrollo de extensiones (funciones) en lenguaje C

Este artículo fue escrito hace un par de años, y no sabía dónde se podía poner, y luego se olvidó.

El significado del uso del lenguaje C cuando se desarrollan extensiones para PostgreSQL en comparación con los lenguajes interpretados (con secuencias de comandos) se puede reducir a dos puntos: rendimiento y funcionalidad. Pero simplemente, el código escrito en C funcionará mucho más rápido, por ejemplo, si la función se llama un millón de veces en la solicitud de cada registro. Más específicamente, algunas características de PostgreSQL no se pueden hacer excepto en C, por ejemplo, en otros lenguajes, los tipos (especialmente si devuelve un valor de una función) ANYELEMENT , ANYARRAY y VARIADIC especialmente importantes no son compatibles .

Función C simple


Por ejemplo, escribimos una función que toma dos argumentos y los agrega. Este ejemplo se describe en la documentación de PostgreSQL, pero lo mejoraremos ligeramente y lo recopilaremos. Luego cargue en PostgreSQL y escriba una llamada desde la función 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);
}

El archivo add_func.c se puede usar como plantilla para desarrollar funcionalidades más complejas. Además, revisa el código

  • #include "postgresql.h": este archivo de encabezado que siempre tendrás que incluir, contiene varios tipos y funciones básicas.
  • #include "fmgr.h": el archivo de encabezado contiene varias macros PG_ *.
  • PG_MODULE_MAGIC: una macro que determina que estamos desarrollando un módulo para PostgreSQL por encima de la versión 8.2.
  • PG_FUNC_INFO_V1: una macro que define convenciones para llamadas a funciones dentro de PostgreSQL. Si declara una función sin ella, habrá un acuerdo para llamar a la Versión 0, de lo contrario, Versión 1.
  • Datum: . , PostgreSQL. - VARIANT Microsoft. «» - . , .
  • add_ab(PG_FUNCTION_ARGS): . - . , .
  • int32 arg_a = PG_GETARG_INT32(0): ( ).
  • PG_RETURN_INT32(arg_a + arg_b): .

Ahora necesitamos compilar y compilar esto correctamente. La salida será una biblioteca compartida cargable dinámicamente (* .so). Para esto, será más conveniente hacerlo a través del Makefile . La documentación describe las claves y las rutas que deben registrarse, pero las recopilaremos con PGXS . Este es un entorno para desarrolladores de extensiones, lo que significa que todos los paquetes necesarios -dev y -devel para PostgreSQL deben estar instalados en su sistema .

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

Creamos la función SQL


Ahora escribiremos una función SQL que se llamará desde nuestra biblioteca de extensiones creada anteriormente.
CREATE FUNCTION add(int, int)
  RETURNS int
AS '/usr/lib/postgresql/9.4/lib/add_func', 'add_ab'
LANGUAGE C STRICT;

¡Eso es todo! Ahora podemos usar esta función para

SELECT add(1, 2);

Automatizar la instalación


Ahora automatizamos un poco la instalación. Esto será muy útil cuando no sepa de antemano qué versión de PostgreSQL se utiliza y de qué manera se instala. Para hacer esto, cree el siguiente archivo,

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

y agregue una línea al Makefile ,

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

Mejorar un poco


Haremos algunos cambios a nuestro ejemplo y haremos algunas mejoras.

Comprobación de argumentos


¿Recuerdas que en la definición SQL de la función add (int, int) utilizamos la palabra clave STRICT ? Significa que si al menos uno de los argumentos es NULL , la función no funcionará y simplemente devolverá NULL . Esto es similar al comportamiento de las declaraciones SQL, por ejemplo, si la declaración "+" tiene al menos un argumento NULL , el resultado será NULL .

Agregamos una comprobación de argumentos a nuestra función, similar a la función de agregación SQL sum () , que ignora los valores NULL y continúa funcionando. Para esto necesitamos hacer

  • Asegúrese de que la función funciona incluso si uno de los argumentos es NULL
  • Si ambos argumentos son NULL , entonces devuelve 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();
}

Y ahora échale un vistazo,

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

Y así es como se puede lograr lo mismo con las herramientas estándar de PostgreSQL,
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

Cualquier cantidad de argumentos en una función


Como ya notó, usamos macros para obtener el valor de los argumentos. Por lo tanto, podemos pasar cualquier número de argumentos y luego simplemente leer sus valores en un bucle,

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

Es posible que tenga una pregunta de inmediato, pero puede usar una matriz para pasar el valor de los argumentos. Y de hecho, esto es lo que hay que hacer, pero desafortunadamente debido a la presencia de su propio administrador de memoria en PostgreSQL, esta no es una tarea tan trivial. Pero trata de resolverlo. Hay un ejemplo en la documentación de PostgreSQL donde se pasa la matriz de caracteres text [], pero esto no es exactamente lo que necesitamos. Tratemos de adaptarnos para nuestros propósitos,

#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();
}

Como siempre, repasemos el código,

  • No hay un tipo especial para matrices enteras, por lo que utilizamos el tipo genérico ArrayType , que funciona para cualquier tipo de matriz.
  • Para inicializar la matriz con el primer argumento, usamos la macro especial PG_GETARG_ARRAYTYPE_P
  • También hay una comprobación para ver si la matriz es realmente unidimensional ARR_NDIM
  • El tipo de OID para int4 (= 23) se define como INT4OID. Para ver definiciones para otros tipos, puede usar SQL,

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


Ahora solo tenemos que enseñarle a PostgreSQL a usar esto declarando una función que tome un argumento int [],

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

Y comprobar

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 |

Recomendaciones


Ahora resumamos algunas de las principales recomendaciones para crear extensiones.

Trabajar con memoria


Los autores y creadores del PostgreSQL DBMS prestan especial atención al trabajo con la memoria y la prevención de fugas. Esta es una de las reglas básicas para desarrollar un DBMS y sus extensiones. Esto se basa en su propia decisión: el contexto de la memoria.

Usando palloc () y pfree ()


Todo el trabajo con memoria en PostgreSQL consiste en el uso de funciones no estándar palloc () y pfree ().

Inicialización de estructuras


Siempre inicialice las nuevas estructuras declaradas. Por ejemplo, llame a memset () después de palloc ().

Archivos conectados


Cada una de sus extensiones debe incluir al menos dos archivos: postgres.h y fmgr.h.

Enlaces útiles


PostgreSQL: Interfaz de programación del servidor .

PostgreSQL: Funciones definidas por el usuario en la red de extensión C

PostgreSQL

All Articles