PostgreSQL: Desenvolvimento de extensões (funções) na linguagem C

Este artigo foi escrito há alguns anos e não sabia onde poderia ser colocado e depois se esqueceu.

O significado do uso da linguagem C ao desenvolver extensões para o PostgreSQL em comparação com as linguagens interpretadas (com script) pode ser reduzido a dois pontos: desempenho e funcionalidade. Mas, simplesmente, o código escrito em C funcionará muito mais rápido, por exemplo, se a função for chamada um milhão de vezes na solicitação para cada registro. Mais especificamente, alguns recursos do PostgreSQL não podem ser executados, exceto em C, por exemplo, em outras linguagens, tipos (especialmente se você retornar um valor de uma função) ANYELEMENT , ANYARRAY e VARIADIC especialmente importante não são suportados .

Função C simples


Por exemplo, escrevemos uma função que pega dois argumentos e os adiciona. Este exemplo está descrito na documentação do PostgreSQL, mas iremos aprimorá-lo levemente e coletá-lo. Em seguida, carregue no PostgreSQL e escreva uma chamada a partir da função 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);
}

O arquivo add_func.c pode ser usado como um modelo para o desenvolvimento de funcionalidades mais complexas. Além disso, siga o código

  • #include "postgresql.h": este arquivo de cabeçalho você sempre terá que incluir, contém vários tipos e funções básicos.
  • #include "fmgr.h": o arquivo de cabeçalho contém várias macros PG_ *.
  • PG_MODULE_MAGIC: uma macro que determina que estamos desenvolvendo um módulo para o PostgreSQL acima da versão 8.2.
  • PG_FUNC_INFO_V1: Uma função de definição de macro que chama convenções no PostgreSQL. Se você declarar uma função sem ela, haverá um acordo em chamar a Versão 0, caso contrário, a Versão 1.
  • Datum: . , PostgreSQL. - VARIANT Microsoft. «» - . , .
  • add_ab(PG_FUNCTION_ARGS): . - . , .
  • int32 arg_a = PG_GETARG_INT32(0): ( ).
  • PG_RETURN_INT32(arg_a + arg_b): .

Agora precisamos compilar e compilar isso corretamente. A saída será uma biblioteca compartilhada dinamicamente carregável (* .so). Para isso, será mais conveniente fazê-lo através do Makefile . A documentação descreve as chaves e os caminhos que precisam ser registrados, mas coletaremos usando o PGXS . Este é um ambiente para desenvolvedores de extensões, o que significa que todos os pacotes -dev e -devel necessários para o PostgreSQL devem estar instalados no seu sistema .

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

Criamos a função SQL


Agora, escreveremos uma função SQL que será chamada da nossa biblioteca de extensões criada anteriormente.
CREATE FUNCTION add(int, int)
  RETURNS int
AS '/usr/lib/postgresql/9.4/lib/add_func', 'add_ab'
LANGUAGE C STRICT;

Isso é tudo! Agora podemos usar esta função para

SELECT add(1, 2);

Automatize a instalação


Agora, automatizamos um pouco a instalação. Isso será muito útil quando você não souber antecipadamente qual versão do PostgreSQL é usada e de que maneira está instalada. Para fazer isso, crie o seguinte arquivo,

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

e adicione uma linha ao Makefile ,

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

Melhore um pouco


Faremos algumas alterações em nosso exemplo e algumas melhorias.

Verificando argumentos


Lembre-se, na definição SQL da função add (int, int) , usamos a palavra-chave STRICT ? Isso significa que, se pelo menos um dos argumentos for NULL , a função não funcionará e simplesmente retornará NULL . Isso é semelhante ao comportamento das instruções SQL, por exemplo, se houver pelo menos um argumento NULL no operador + , o resultado será NULL . Adicionamos uma verificação de argumento à nossa função, semelhante à função de agregação SQL sum () , que ignora valores NULL e continua a funcionar. Para isso, precisamos fazer



  • Verifique se a função funciona mesmo se um dos argumentos for NULL
  • Se ambos os argumentos forem NULL , retorne 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();
}

E agora confira,

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

E aqui está como o mesmo pode ser alcançado com as ferramentas padrão do 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

Qualquer número de argumentos em uma função


Como você já percebeu, usamos macros para obter o valor dos argumentos. Portanto, podemos passar qualquer número de argumentos e apenas ler seus valores em um loop,

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

Você pode ter uma pergunta imediatamente, mas pode usar uma matriz para passar o valor dos argumentos. E, de fato, é isso que precisa ser feito, mas infelizmente devido à presença de seu próprio gerenciador de memória no PostgreSQL, essa não é uma tarefa tão trivial. Mas tente resolvê-lo. Há um exemplo na documentação do PostgreSQL onde a matriz de caracteres de texto [] é passada, mas isso não é exatamente o que precisamos. Vamos tentar nos adaptar aos nossos 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 sempre, vamos analisar o código,

  • Como não existe um tipo especial para matrizes inteiras, usamos o tipo genérico ArrayType , que funciona para qualquer tipo de matriz
  • Para inicializar a matriz com o primeiro argumento, usamos a macro especial PG_GETARG_ARRAYTYPE_P
  • Há também uma verificação para ver se a matriz é realmente unidimensional ARR_NDIM
  • O tipo de OID para int4 (= 23) é definido como INT4OID. Para visualizar definições para outros tipos, você pode usar SQL,

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


Agora, apenas precisamos ensinar o PostgreSQL a usar isso declarando uma função que aceita um argumento int [],

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

E verifique

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 |

Recomendações


Agora vamos resumir algumas das principais recomendações para a criação de extensões.

Trabalhar com memória


Os autores e criadores do PostgreSQL DBMS prestam atenção especial ao trabalho com memória e à prevenção de vazamentos. Essa é uma das regras básicas para o desenvolvimento de um DBMS e suas extensões. Isso se baseia em sua própria decisão - o contexto da memória.

Usando palloc () e pfree ()


Todo o trabalho com memória no PostgreSQL consiste no uso de funções não-padrão palloc () e pfree ().

Inicialização de Estruturas


Sempre inicialize novas estruturas declaradas. Por exemplo, chame memset () após palloc ().

Arquivos conectados


Cada uma das suas extensões deve incluir pelo menos dois arquivos: postgres.he fmgr.h.

Links Úteis


PostgreSQL: Interface de programação do servidor .

PostgreSQL: Funções definidas pelo usuário na rede de extensão C do

PostgreSQL

All Articles