As funções definidas pelo usuário podem ser escritas em C (ou em uma linguagem compatível com C, como C++). Estas funções são compiladas em objetos dinamicamente carregáveis (também chamados de bibliotecas compartilhadas), carregados pelo servidor sob demanda. O recurso de carregamento dinâmico é o que distingue as “funções na linguagem C” das “funções internas” — as convenções de codificação usadas são essencialmente as mesmas para as duas. (Portanto, a biblioteca de funções internas padrão é uma rica fonte de exemplos de codificação para funções C definidas pelo usuário.)
No momento, é usada apenas uma convenção de chamada para funções
escritas em C (“versão 1”).
O suporte para esta convenção de chamada é indicado escrevendo a
chamada da macro PG_FUNCTION_INFO_V1() na
função C, conforme mostrado abaixo.
Na primeira vez que a função definida pelo usuário, em um
determinado arquivo objeto carregável, é chamada em uma sessão,
o carregador dinâmico carrega este arquivo objeto na memória
para que a função possa ser chamada.
O comando CREATE FUNCTION para uma função
C definida pelo usuário deve, portanto,
especificar duas informações para a função:
o nome do arquivo objeto carregável, e o nome C
(símbolo de ligação) da função específica a ser chamada neste
arquivo objeto.
Se o nome C não for especificado explicitamente,
presume-se que seja idêntico ao nome da função SQL.
O seguinte algoritmo é usado para localizar o arquivo objeto
compartilhado com base no nome fornecido no comando
CREATE FUNCTION:
Se o nome for um caminho absoluto, o arquivo indicado será carregado.
Se o nome começar com a cadeia de caracteres
$libdir, esta parte será substituída pelo nome
do diretório da biblioteca de pacotes do
PostgreSQL, determinado em tempo
de construção.
Se o nome não contiver a parte do diretório, o arquivo é procurado no caminho especificado pela variável de configuração dynamic_library_path.
Caso contrário (o arquivo não foi encontrado no caminho, ou a parte do diretório não é absoluta), o carregador dinâmico tentará usar o nome fornecido, o que provavelmente vai falhar. (Não é confiável depender do diretório de trabalho atual.)
Se esta sequência não funcionar, a extensão de nome de arquivo de
biblioteca compartilhada específica da plataforma (geralmente
.so) será anexada ao nome fornecido, e esta
sequência será tentada novamente.
Se isto também falhar, a carga falhará.
Recomenda-se localizar as bibliotecas compartilhadas em relação a
$libdir, ou através do caminho de biblioteca
dinâmica.
Isto simplifica as atualizações de versão, se a nova instalação
estiver em um local diferente.
O diretório real representado por $libdir pode
ser encontrado com o comando pg_config --pkglibdir.
O ID de usuário que o servidor PostgreSQL executa deve poder percorrer o caminho para o arquivo que se pretende carregar. Tornar o arquivo, ou um diretório de nível superior, não legível e/ou não executável pelo usuário postgres, é um erro comum.
Em qualquer um dos casos, o nome do arquivo fornecido no comando
CREATE FUNCTION é registrado literalmente nos
catálogos do sistema, portanto, se o arquivo precisar ser carregado
novamente, o mesmo procedimento será aplicado.
O PostgreSQL não compila uma função
C automaticamente.
O arquivo-fonte deve ser compilado antes de ser referenciado em um
comando CREATE FUNCTION.
Veja Compilação e ligação de funções carregadas dinamicamente para obter informações adicionais.
Para garantir que o arquivo objeto carregado dinamicamente não
seja carregado em um servidor incompatível, o
PostgreSQL verifica se o arquivo contém
o “bloco mágico” com o conteúdo apropriado.
O bloco mágico permite que o servidor detecte incompatibilidades
óbvias, como código compilado para uma versão principal do
PostgreSQL diferente.
Para incluir o bloco mágico, escreva-o em um (e apenas um) dos
arquivos-fonte do módulo, após ter incluído o cabeçalho
fmgr.h:
PG_MODULE_MAGIC;
ou
PG_MODULE_MAGIC_EXT(parameters);
A variante PG_MODULE_MAGIC_EXT permite a
especificação de informações adicionais sobre o módulo;
No momento, é possível adicionar um nome e/ou uma cadeia de
caracteres de versão.
(Mais campos poderão ser permitidos no futuro.)
Deve ser escrito algo como isto:
PG_MODULE_MAGIC_EXT(
.name = "nome_do_meu_módulo",
.version = "1.2.3"
);
Em seguida, o nome e a versão podem ser examinados através da
função pg_get_loaded_modules().
O significado da cadeia de caracteres de versão não é restrito pelo
PostgreSQL, mas se recomenda o uso
de regras semânticas de versão.
Após ser usado pela primeira vez, o arquivo objeto carregado dinamicamente permanece retido na memória. Chamadas futuras na mesma sessão para a(s) função(ões) neste arquivo incorrerão apenas na pequena sobrecarga de uma procura na tabela de símbolos. Se for necessário forçar o recarregamento de um arquivo objeto, por exemplo, após recompilá-lo, deve ser iniciada uma nova sessão.
Opcionalmente, um arquivo carregado dinamicamente pode conter uma
função de inicialização.
Se o arquivo incluir uma função chamada _PG_init,
esta função será chamada imediatamente após o carregamento do arquivo.
Esta função não recebe nenhum parâmetros e deve retornar
void.
No momento não existe forma de descarregar um arquivo carregado
dinamicamente.
Para saber como escrever funções na linguagem C, é necessário saber como o PostgreSQL representa internamente os tipos de dados base, e como eles podem ser passados de e para as funções. Internamente, o PostgreSQL considera um tipo de dados base como um “blob de memória”. Por sua vez, as funções definidas pelo usuário definidas sobre um tipo de dados, definem a maneira como o PostgreSQL pode operar neste tipo de dados. Ou seja, o PostgreSQL apenas armazena e recupera os dados do disco, e usa as funções definidas pelo usuário para inserir, processar e enviar os dados.
Os tipos de dados base podem ter um dos três formatos internos:
passado por valor, comprimento fixo
passado por referência, comprimento fixo
passado por referência, comprimento variável
Os tipos de dados passados por valor podem ter apenas 1, 2 ou 4
bytes de comprimento
(também 8 bytes, se sizeof(Datum) for 8 na máquina).
Deve-se ter o cuidado de definir os tipos de dados de forma que
tenham o mesmo tamanho (em bytes) em todas as arquiteturas.
Por exemplo, o tipo de dados long é perigoso,
porque tem 4 bytes em algumas máquinas e 8 bytes em outras, enquanto
o tipo de dados int tem 4 bytes na maioria das máquinas
Unix.
Uma implementação razoável do tipo de dados int4 em
máquinas Unix pode ser:
/* inteiro de 4 bytes, passado por valor */ typedef int int4;
(O código real C do
PostgreSQL chama este tipo de dados de
int32, porque há uma convenção na linguagem
C que int
significa XXXX bits
Da mesma forma, note também que o tipo de dados C
int8 tem o tamanho de 1 byte.
O tipo de dados SQL int8 é chamado de
int64 na linguagem C.
Veja também Tabela 36.2.)
[115]
Por outro lado, os tipos de dados de comprimento fixo de qualquer tamanho podem ser passados por referência. Por exemplo, aqui está um exemplo de implementação de um tipo de dados do PostgreSQL:
/* estrutura de 16 bytes, passada por referência */
typedef struct
{
double x, y;
} Point;
Somente podem ser usados ponteiros para estes tipos de dados ao passá-los
dentro e fora das funções do PostgreSQL.
Para retornar um valor desse tipo de dados, deve ser alocada a
quantidade certa de memória com palloc,
preenchida a memória alocada, e retornado um ponteiro para ela.
(Além disso, se for desejado retornar apenas o mesmo valor de um dos
argumentos de entrada que é do mesmo tipo de dados, pode ser saltado
o palloc extra, e apenas retornado um ponteiro
para o valor de entrada.)
Finalmente, todos os tipos de dados de comprimento variável também
devem ser passados por referência, e devem começar com um campo
de tamanho opaco de exatamente 4 bytes, que será definido por
SET_VARSIZE; nunca defina este campo diretamente!
Todos os dados a serem armazenados dentro desse tipo de dados devem estar
localizados na memória imediatamente após este campo de comprimento.
O campo de comprimento contém o comprimento total da estrutura,
ou seja, inclui o tamanho do próprio campo de comprimento.
Outro ponto importante é evitar deixar bits não
inicializados dentro dos valores de tipo de dados; por exemplo,
cuide para zerar quaisquer bytes de preenchimento de alinhamento
que possam estar presentes em struct.
Sem isto, constantes logicamente equivalentes de seu tipo de dados
podem ser vistas como desiguais pelo planejador, levando a planos
ineficientes (embora não incorretos).
Nunca modifique o conteúdo de um valor de entrada passado por referência. Se isto for feito, provavelmente os dados no disco serão corrompidos, porque o ponteiro recebido pode apontar diretamente para um buffer de disco. A única exceção a esta regra é explicada em Agregações definidas pelo usuário.
Como exemplo, o tipo de dados text pode ser definido da
seguinte forma:
typedef struct {
int32 length;
char data[FLEXIBLE_ARRAY_MEMBER];
} text;
A notação [FLEXIBLE_ARRAY_MEMBER] significa que
o comprimento real da parte de dados não é especificado por esta
declaração.
Ao manusear tipos de dados de comprimento variável, deve-se cuidar
para alocar a quantidade correta de memória, e definir o campo de
comprimento corretamente.
Por exemplo, se quisermos armazenar 40 bytes em uma estrutura
text, podemos usar um fragmento de código
como este:
#include "postgres.h" ... char buffer[40]; /* dados armazenados */ ... text *destination = (text *) palloc(VARHDRSZ + 40); SET_VARSIZE(destination, VARHDRSZ + 40); memcpy(destination->data, buffer, 40); ...
VARHDRSZ é o mesmo que
sizeof(int32), mas é considerado um bom estilo
usar a macro VARHDRSZ para se referir ao tamanho
da sobrecarga para um tipo de dados de comprimento variável.
Além disso, o campo de comprimento deve ser
definido usando a macro SET_VARSIZE, e não por
simples atribuição.
A Tabela 36.2 mostra os tipos de dados
C correspondentes a muitos dos tipos de dados
SQL nativos do
PostgreSQL.
A coluna “Definido em” indica o arquivo de cabeçalho
que precisa ser incluído para obter a definição do tipo de dados.
(A definição real pode estar em um arquivo diferente incluído no
arquivo listado.
Recomenda-se que os usuários sigam a interface definida.)
Note que se deve sempre incluir postgres.h
primeiro em qualquer arquivo-fonte do código do servidor, porque
este arquivo declara uma série de coisas sempre necessárias,
e porque incluir outros cabeçalhos primeiro pode causar
problemas de portabilidade.
Tabela 36.2. Tipos C equivalentes para tipos de dados SQL integrados
| Tipo SQL | Tipo C | Definido em |
|---|---|---|
boolean | bool | postgres.h (talvez integrado ao compilador) |
box | BOX* | utils/geo_decls.h |
bytea | bytea* | postgres.h |
"char" | char | (nativo do compilador) |
character | BpChar* | postgres.h |
cid | CommandId | postgres.h |
date | DateADT | utils/date.h |
float4 (real) | float4 | postgres.h |
float8 (double precision) | float8 | postgres.h |
int2 (smallint) | int16 | postgres.h |
int4 (integer) | int32 | postgres.h |
int8 (bigint) | int64 | postgres.h |
interval | Interval* | datatype/timestamp.h |
lseg | LSEG* | utils/geo_decls.h |
name | Name | postgres.h |
numeric | Numeric | utils/numeric.h |
oid | Oid | postgres.h |
oidvector | oidvector* | postgres.h |
path | PATH* | utils/geo_decls.h |
point | POINT* | utils/geo_decls.h |
regproc | RegProcedure | postgres.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 |
timestamp with time zone | TimestampTz | datatype/timestamp.h |
varchar | VarChar* | postgres.h |
xid | TransactionId | postgres.h |
Agora que foram examinadas todas as estruturas possíveis para os tipos de dados base, podem ser mostrados alguns exemplos de funções
A convenção de chamada versão 1 conta com macros para suprimir grande parte da complexidade de passar argumentos e resultados. A declaração C de uma função versão 1 é sempre:
Datum funcname(PG_FUNCTION_ARGS)
Além disso, a chamada de macro
PG_FUNCTION_INFO_V1(funcname);
deve aparecer no mesmo arquivo-fonte.
(Por convenção, é escrito logo antes da própria função.)
Esta chamada de macro não é necessária para funções na linguagem
internal, porque o
PostgreSQL assume que todas as funções
internas usam a convenção versão 1.
Entretanto, é necessário para funções carregadas dinamicamente.
Em uma função versão 1, cada argumento real é obtido usando a
macro PG_GETARG_
correspondente ao tipo de dados do argumento.
(Em funções não estritas, deve haver uma verificação prévia sobre
a nulidade do argumento, usando xxx()PG_ARGISNULL();
veja abaixo.)
O resultado é retornado usando a macro
PG_RETURN_
para o tipo de dados retornado.
xxx()PG_GETARG_
recebe como argumento o número do argumento da função a ser buscado,
onde a contagem começa em 0.
xxx()PG_RETURN_
recebe como argumento o valor real a ser retornado.
xxx()
Para chamar outra função da versão 1, pode-se usar
DirectFunctionCall.
Isto é particularmente útil quando se deseja chamar funções
definidas na biblioteca interna padrão, usando uma interface
semelhante à sua assinatura SQL.
n(func,
arg1, ..., argn)
Estas funções de conveniência e outras semelhantes podem ser
encontradas no arquivo fmgr.h.
A família de funções
DirectFunctionCall
esperam que o primeiro argumento seja o nome de uma função escrita
em C.
Existe também a função
nOidFunctionCall
que recebe o OID da função alvo, além de algumas outras variantes.
Todas estas opções esperam que os argumentos da função sejam
fornecidos como nDatum, e da mesma forma elas retornam
Datum.
Observe que nem os argumentos nem o resultado podem ser
NULL ao usar estas funções auxiliares.
Por exemplo, para chamar a função
starts_with(text, text)
a partir do C, pode-se pesquisar no catálogo
e descobrir que sua implementação em C é a função.
Datum text_starts_with(PG_FUNCTION_ARGS)
Normalmente se usaria
DirectFunctionCall2(text_starts_with, ...)
para chamar esta função.
Entretanto, starts_with(text, text) requer
informação de ordenação, portanto irá falhará com a mensagem
“não foi possível determinar qual ordenação usar para a
comparação de cadeias de caracteres.” se for chamada dessa
maneira.
Em vez disso, deve-se usar
DirectFunctionCall2Coll(text_starts_with, ...)
e fornecer a ordenação desejada, que normalmente é simplesmente
repassada de PG_GET_COLLATION(), como mostrado
no exemplo abaixo.
O arquivo fmgr.h também fornece macros que
facilitam conversões entre tipos de dados C e
Datum.
Por exemplo, para converter Datum em text*,
pode-se usar DatumGetTextPP(X).
Embora alguns tipos de dados tenham macros com nomes como
TypeGetDatum(X) para a conversão inversa,
text* não tem; é suficiente usar a macro genérica
PointerGetDatum(X) para isto.
Se a extensão definir tipos de dados adicionais, geralmente é
conveniente definir macros semelhantes também para estes
tipos de dados.
A seguir estão alguns exemplos usando a convenção de chamada versão 1:
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
#include "varatt.h"
PG_MODULE_MAGIC;
/* por valor */
PG_FUNCTION_INFO_V1(add_one);
Datum
add_one(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
PG_RETURN_INT32(arg + 1);
}
/* por referência, comprimento fixo */
PG_FUNCTION_INFO_V1(add_one_float8);
Datum
add_one_float8(PG_FUNCTION_ARGS)
{
/* As macros para FLOAT8 ocultam sua natureza de passagem por referência. */
float8 arg = PG_GETARG_FLOAT8(0);
PG_RETURN_FLOAT8(arg + 1.0);
}
PG_FUNCTION_INFO_V1(makepoint);
Datum
makepoint(PG_FUNCTION_ARGS)
{
/* Aqui, a natureza de passagem por referência de Point não é ocultada. */
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);
}
/* por referência, comprimento variável */
PG_FUNCTION_INFO_V1(copytext);
Datum
copytext(PG_FUNCTION_ARGS)
{
text *t = PG_GETARG_TEXT_PP(0);
/*
* VARSIZE_ANY_EXHDR é o tamanho da estrutura em bytes, menos
* VARHDRSZ ou VARHDRSZ_SHORT de seu cabeçalho.
* Construir a cópia com comprimento integral do cabeçalhp.
*/
text *new_t = (text *) palloc(VARSIZE_ANY_EXHDR(t) + VARHDRSZ);
SET_VARSIZE(new_t, VARSIZE_ANY_EXHDR(t) + VARHDRSZ);
/*
* VARDATA é um ponteiro para a região de dados da nova estrutura.
* A fonte pode ser um dado curto, portanto seus dados devem
* ser recuperados através de VARDATA_ANY.
*/
memcpy(VARDATA(new_t), /* destino */
VARDATA_ANY(t), /* origem */
VARSIZE_ANY_EXHDR(t)); /* quantidade de bytes */
PG_RETURN_TEXT_P(new_t);
}
PG_FUNCTION_INFO_V1(concat_text);
Datum
concat_text(PG_FUNCTION_ARGS)
{
text *arg1 = PG_GETARG_TEXT_PP(0);
text *arg2 = PG_GETARG_TEXT_PP(1);
int32 arg1_size = VARSIZE_ANY_EXHDR(arg1);
int32 arg2_size = VARSIZE_ANY_EXHDR(arg2);
int32 new_text_size = arg1_size + arg2_size + VARHDRSZ;
text *new_text = (text *) palloc(new_text_size);
SET_VARSIZE(new_text, new_text_size);
memcpy(VARDATA(new_text), VARDATA_ANY(arg1), arg1_size);
memcpy(VARDATA(new_text) + arg1_size, VARDATA_ANY(arg2), arg2_size);
PG_RETURN_TEXT_P(new_text);
}
/* Um invólucro em torno de starts_with(text, text) */
PG_FUNCTION_INFO_V1(t_starts_with);
Datum
t_starts_with(PG_FUNCTION_ARGS)
{
text *t1 = PG_GETARG_TEXT_PP(0);
text *t2 = PG_GETARG_TEXT_PP(1);
Oid collid = PG_GET_COLLATION();
bool result;
result = DatumGetBool(DirectFunctionCall2Coll(text_starts_with,
collid,
PointerGetDatum(t1),
PointerGetDatum(t2)));
PG_RETURN_BOOL(result);
}
Supondo que o código acima tenha sido editado no arquivo
funcs.c
[116],
e compilado em um objeto compartilhado, as funções para o
PostgreSQL podem ser definidas
com comandos como estes:
CREATE FUNCTION add_one(integer) RETURNS integer
AS 'DIRECTORY/funcs', 'add_one'
LANGUAGE C STRICT;
-- note a sobrecarga do nome da função 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;
CREATE FUNCTION t_starts_with(text, text) RETURNS boolean
AS 'DIRECTORY/funcs', 't_starts_with'
LANGUAGE C STRICT;
Nos comandos acima, DIRECTORY representa
o diretório dos arquivos de biblioteca compartilhada (por exemplo,
o diretório src/inlude da
distribuição do código-fonte do PostgreSQL).
(Um estilo melhor seria usar apenas 'funcs'
na cláusula AS, após adicionar
DIRECTORY ao caminho de procura.
Em qualquer um dos casos, pode ser omitida a extensão específica
do sistema para biblioteca compartilhada, geralmente
.so.)
Note que as funções foram especificadas como “STRICT”,
significando que o sistema deve assumir
automaticamente um resultado nulo se qualquer valor de entrada for nulo.
Ao fazer isto, se evita ter que verificar entradas nulas no código
da função. Sem isto, teríamos que verificar valores nulos
explicitamente, usando PG_ARGISNULL().
A macro PG_ARGISNULL(
permite a função testar se cada entrada é nula.
(É claro que só há necessidade de fazer isto em funções não
declaradas como “STRICT”.) Assim como nas macros
n)PG_GETARG_,
os argumentos de entrada são contados começando em zero.
Note que se deve parar de executar
xxx()PG_GETARG_
até ser verificado que o argumento não é nulo. Para retornar um
resultado nulo, execute xxx()PG_RETURN_NULL();
isto funciona em funções estritas e não estritas.
À primeira vista, as convenções de codificação versão 1 podem parecer apenas obscurantismo sem sentido, em comparação com o uso de convenções de chamada C simples. No entanto, estas convenções permitem lidar com valores de argumentos e valores retornados que podem ser nulos, e valores toast (compactados ou fora de linha).
Outras opções fornecidas pela interface versão 1 são duas
variantes das macros
PG_GETARG_.
A primeira delas,
xxx()PG_GETARG_,
garante retornar uma cópia do argumento especificado que pode ser
escrita com segurança.
(Às vezes, as macros normais retornam um ponteiro para um valor
que está fisicamente armazenado em uma tabela, onde não deve
ser escrito. O uso das macros
xxx_COPY()PG_GETARG_
garante um resultado que pode ser escrito.)
A segunda variante consiste nas macros
xxx_COPY()PG_GETARG_
que recebem três argumentos.
O primeiro é o número do argumento da função (como acima).
O segundo e o terceiro argumentos são o deslocamento e o
comprimento do segmento a ser retornado.
Os deslocamentos são contados a partir de zero, e um comprimento
negativo solicita que o restante do valor seja retornado.
Estas macros fornecem acesso mais eficiente a partes de grandes
valores no caso de terem o tipo de armazenamento “external”.
(O tipo de armazenamento de uma coluna pode ser especificado usando
xxx_SLICE()ALTER TABLE , onde
nome_da_tabela ALTER
COLUMN nome_da_coluna SET STORAGE
tipo_de_armazenamentotipo_de_armazenamento é um entre
plain, external, extended,
ou main.)
Finalmente, as convenções de chamada de função versão 1 permitem
retornar resultados de conjunto
(Retorno de conjuntos),
implementar funções de gatilho (Gatilhos), e
tratadores de chamadas em linguagem procedural
(Escrita de tratador de linguagem procedural).
Para obter mais detalhes veja o arquivo
src/backend/utils/fmgr/README na distribuição
do código-fonte.
Antes de seguirmos para tópicos mais avançados, devemos discutir algumas regras de codificação para as funções do PostgreSQL escritas na linguagem C. Embora seja possível carregar funções escritas em linguagens diferentes de C no PostgreSQL, isto é geralmente difícil (quando não impossível) porque outras linguagens, como C++, FORTRAN ou Pascal, geralmente não seguem a mesma convenção de chamada do C. Ou seja, outras linguagens não passam argumentos e retornam valores entre funções da mesma forma. Por este motivo, é assumido que suas funções em linguagem C são realmente escritas em C.
As regras básicas para escrever e construir funções C são as seguintes:
Use pg_config --includedir-server
para descobrir onde os arquivos de cabeçalho do servidor
PostgreSQL estão instalados em seu
sistema (ou no sistema onde seus usuários estarão executando).
Compilar e vincular o código para poder ser carregado dinamicamente no PostgreSQL sempre requer sinalizadores especiais. Veja Compilação e ligação de funções carregadas dinamicamente para obter uma explicação detalhada sobre como fazer isto no seu sistema operacional específico.
Lembre-se de definir o “bloco mágico” para a biblioteca compartilhada, conforme descrito em Carregamento dinâmico.
Ao alocar memória, use as funções do
PostgreSQL palloc
e pfree em vez das funções correspondentes
da biblioteca C malloc e
free.
A memória alocada por palloc será liberada
automaticamente ao final de cada transação, evitando vazamento
de memória.
Sempre devem ser zerados os bytes das estruturas usando
memset
(ou alocado com palloc0 antes de tudo).
Mesmo atribuindo valor a cada campo da estrutura, pode haver
preenchimento de alinhamento (buracos na estrutura) contendo
valores inúteis.
Sem isto, fica difícil oferecer suporte a índices de
hash, ou junções de hash,
porque devem ser selecionados apenas os bits significativos da
estrutura de dados para calcular o hash.
Às vezes, o planejador também depende da comparação de constantes
por meio da igualdade bit a bit, portanto, podem ser obtidos
resultados de planejamento indesejáveis se valores logicamente
equivalentes não forem iguais bit a bit.
A maioria dos tipos de dados internos do
PostgreSQL
são declarados no arquivo postgres.h,
enquanto as interfaces do gerenciador de função
(PG_FUNCTION_ARGS, etc.) estão no arquivo
fmgr.h,
portanto é necessário incluir pelo menos estes dois arquivos.
Por motivos de portabilidade, é melhor incluir o arquivo
postgres.h primeiro,
antes de qualquer outro arquivo de cabeçalho do sistema ou do
usuário.
Incluir postgres.h também inclui
elog.h e palloc.h.
Os nomes dos símbolos definidos nos arquivos objeto não devem entrar em conflito entre si, ou com os símbolos definidos no executável do servidor PostgreSQL. Será necessário renomear as funções ou variáveis caso se receba mensagens de erro neste sentido.
Antes que se possa usar as funções de extensão do PostgreSQL escritas em C, estas funções devem ser compiladas e vinculadas de uma maneira especial para produzir um arquivo que possa ser carregado dinamicamente pelo servidor. Falando precisamente, há necessidade de se criar uma biblioteca compartilhada.
Para obter informações além do que está contido nesta seção, deve ser
lida a documentação do sistema operacional, em particular as páginas
de manual do compilador C
(cc),
do vinculador
(ld),
e do vinculador/carregador dinâmico
(ld.so).
Além disso, o código-fonte do PostgreSQL
contém vários exemplos funcionais no diretório contrib.
Entretanto, se ficar dependente desses exemplos, tornará os módulos
dependentes da disponibilidade do código-fonte do
PostgreSQL.
A criação de bibliotecas compartilhadas é geralmente análoga à vinculação de executáveis: primeiro os arquivos contendo o código-fonte são compilados em arquivos objeto e, em seguida, os arquivos objeto são vinculados. Os arquivos objeto precisam ser criados como código independente de posição (position-independent code, PIC), significando conceitualmente que podem ser colocados em um local arbitrário na memória quando são carregados pelo executável. (Arquivos objeto destinados a executáveis geralmente não são compilados dessa maneira.) O comando para vincular uma biblioteca compartilhada contém sinalizadores especiais para distingui-lo da vinculação de um executável (pelo menos em teoria — em alguns sistemas a prática é muito mais feia).
Nos exemplos a seguir, é assumido que o código-fonte está no arquivo
foo.c, e será criada a biblioteca compartilhada
foo.so.
O arquivo objeto intermediário será chamado foo.o,
a menos que esteja indicado de outra forma.
Uma biblioteca compartilhada pode conter mais de um arquivo objeto,
mas será usado apenas um aqui.
O sinalizador do compilador para criar PIC é
-fPIC.
Para criar bibliotecas compartilhadas, o sinalizador do compilador
é -shared.
cc -fPIC -c foo.c cc -shared -o foo.so foo.o
Isto é aplicável a partir da versão 13.0 do
FreeBSD, as versões mais
antigas usavam o compilador gcc.
O sinalizador do compilador para criar PIC é
-fPIC.
O sinalizador do compilador para criar uma biblioteca
compartilhada é -shared.
Um exemplo completo se parece com:
cc -fPIC -c foo.c cc -shared -o foo.so foo.o
A seguir está um exemplo que assume estarem instaladas as ferramentas do desenvolvedor.
cc -c foo.c cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o
O sinalizador do compilador para criar PIC é
-fPIC.
Para sistemas ELF, é usado o compilador com o
sinalizador -shared para vincular bibliotecas
compartilhadas.
Para os sistemas antigos, não-ELF, é usado
ld -Bshareable.
gcc -fPIC -c foo.c gcc -shared -o foo.so foo.o
O sinalizador do compilador para criar PIC é
-fPIC.
É usado ld -Bshareable para vincular
bibliotecas compartilhadas.
gcc -fPIC -c foo.c ld -Bshareable -o foo.so foo.o
O sinalizador do compilador para criar PIC é
-KPIC com o compilador da SUN, e
-fPIC com o GCC.
Para vincular bibliotecas compartilhadas, a opção do compilador é
-G nos dois compiladores, ou como alternativa
-shared no GCC.
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
Se achar isto muito complicado, considere usar a GNU Libtool, que esconde as diferenças de plataforma por trás de uma interface uniforme.
O arquivo de biblioteca compartilhada resultante pode ser então
carregado no PostgreSQL.
Ao especificar o nome do arquivo para o comando
CREATE FUNCTION, deve-se fornecer o nome do arquivo
da biblioteca compartilhada, e não o arquivo objeto intermediário.
Note que a extensão de biblioteca compartilhada padrão do sistema
(geralmente .so ou .sl)
pode ser omitida do comando CREATE FUNCTION,
e normalmente deve ser omitido para uma melhor portabilidade.
Veja em Carregamento dinâmico onde o servidor espera encontrar os arquivos de biblioteca compartilhada.
Esta seção contém orientações para autores de extensões e outros plugins de servidor sobre a estabilidade da API e da ABI no servidor PostgreSQL.
O servidor PostgreSQL contém diversas APIs bem definidas para plugins de servidor, como o gerenciador de funções (fmgr, descrito neste capítulo), SPI (Interface de programação servidor), e vários ganchos projetados especificamente para extensões. Estas interfaces são cuidadosamente gerenciadas para garantir estabilidade e compatibilidade a longo prazo. Entretanto, todo o conjunto de funções e variáveis globais no servidor constitui efetivamente a API publicamente utilizável, e a maior parte dela não foi projetada com a extensibilidade e a estabilidade a longo prazo em mente.
Portanto, embora aproveitar estas interfaces seja válido, quanto mais alguém se afasta do caminho já trilhado, maior a probabilidade de encontrar problemas de compatibilidade de API ou de ABI em algum momento. Os autores de extensões são incentivados a fornecer informações sobre seus requisitos, para que, com o tempo, à medida que novos padrões de uso surgirem, certas interfaces possam ser consideradas mais estáveis ou novas interfaces, com melhor projeto, possam ser adicionadas.
A API (Application Programming Interface), ou Interface de Programação de Aplicações, é a interface usada em tempo de compilação.
Não há nenhuma garantia de compatibilidade
de API entre as versões principais do
PostgreSQL.
Portanto, o código da extensão pode exigir alterações no
código-fonte para funcionar com várias versões principais.
Estes problemas geralmente podem ser gerenciados com condições
de pré-processador, como
#if PG_VERSION_NUM >= 160000.
Extensões sofisticadas que utilizam interfaces além das bem
definidas geralmente exigem algumas dessas alterações para
cada versão principal do servidor.
O PostgreSQL se esforça para evitar quebras na API do servidor em versões secundárias. Em geral, o código de extensão que compila e funciona com uma versão secundária também deve compilar e funcionar com qualquer outra versão secundária da mesma versão principal, passada ou futura.
Quando uma alteração for necessária, ela será cuidadosamente gerenciada, levando em consideração os requisitos das extensões. Estas alterações serão comunicadas nas notas de lançamento (Notas da versão).
A ABI (Application Binary Interface), ou Interface Binária de Aplicação, é a interface usada em tempo de execução.
Servidores de diferentes versões principais possuem
ABIs intencionalmente incompatíveis.
Portanto, as extensões que utilizam APIs de
servidor devem ser recompiladas para cada versão principal.
A inclusão do PG_MODULE_MAGIC
(veja Carregamento dinâmico) garante que o código
compilado para uma versão principal seja rejeitado por outras
versões principais.
O PostgreSQL se esforça para evitar quebras de ABI do servidor em versões secundárias. Em geral, uma extensão compilada para qualquer versão secundária deve funcionar com qualquer outra versão secundária da mesma versão principal, passada ou futura.
Quando uma alteração for necessária, o PostgreSQL escolherá a alteração menos invasiva possível, por exemplo, inserindo um novo campo no espaço de preenchimento ou anexando-o ao final de uma estrutura. Este tipo de alteração não deve afetar as extensões, a menos que usem padrões de código muito incomuns.
Porém, em casos raros, mesmo estas modificações minimamente invasivas podem se mostrar impraticáveis ou impossíveis. Neste caso, a modificação será cuidadosamente gerenciada, levando em consideração as necessidades das extensões. Estas alterações também serão documentadas nas notas de lançamento (Notas da versão).
Observe, no entanto, que muitas partes do servidor não são projetadas ou mantidas como APIs para acesso público (e que, na maioria dos casos, o limite real também não está bem definido). Caso surjam necessidades urgentes, as alterações nessas partes serão naturalmente feitas com menos consideração pelo código da extensão do que as alterações em interfaces bem definidas e amplamente utilizadas.
Além disso, na ausência de detecção automatizada dessas alterações, isto não é uma garantia, mas historicamente estas alterações que causam incompatibilidade têm sido extremamente raras.
Os tipos de dados compostos não têm uma disposição fixa como as estruturas C. As instâncias de um tipo de dados composto podem conter campos nulos. Além disso, os tipos de dados compostos que fazem parte de uma hierarquia de herança podem ter campos diferentes dos outros membros da mesma hierarquia de herança. Portanto, o PostgreSQL fornece uma interface de função para acessar os campos de tipos de dados compostos na linguagem C.
Suponha que se deseja escrever uma função para responder à consulta:
SELECT name, c_overpaid(emp, 1500) AS overpaid
FROM emp
WHERE name = 'Bill' OR name = 'Sam';
Usando as convenções de chamada versão 1, pode-se definir
c_overpaid como:
#include "postgres.h"
#include "executor/executor.h" /* para GetAttributeByName() */
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(c_overpaid);
Datum
c_overpaid(PG_FUNCTION_ARGS)
{
HeapTupleHeader t = PG_GETARG_HEAPTUPLEHEADER(0);
int32 limit = PG_GETARG_INT32(1);
bool isnull;
Datum salary;
salary = GetAttributeByName(t, "salary", &isnull);
if (isnull)
PG_RETURN_BOOL(false);
/* Como alternativa, pode-se preferir usar PG_RETURN_NULL() para salários nulos. */
PG_RETURN_BOOL(DatumGetInt32(salary) > limit);
}
GetAttributeByName é a função do sistema
PostgreSQL que retorna os atributos
da linha especificada.
Possui três argumentos:
um argumento do tipo HeapTupleHeader passado para a
função, o nome do atributo desejado, e um parâmetro de retorno
informando se o atributo é nulo.
GetAttributeByName retorna um valor
Datum que pode ser convertido para o tipo de dados
apropriado usando a função
DatumGet
apropriada.
Note que o valor retornado não tem sentido se o sinalizador
de nulo estiver definido; sempre deve ser verificado o sinalizador
de nulo antes de tentar fazer qualquer coisa com o resultado.
XXX()
Também existe a função GetAttributeByNum,
que seleciona o atributo de destino pelo número da coluna,
em vez do nome.
O comando a seguir declara a função c_overpaid
no SQL:
CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
AS 'DIRECTORY/funcs', 'c_overpaid'
LANGUAGE C STRICT;
Repare que foi usado STRICT para não ser
necessário verificar se os argumentos de entrada são nulos.
Para retornar uma linha, ou um valor de tipo de dados composto, em uma função na linguagem C, pode ser usada uma API especial que fornece macros e funções para ocultar grande parte da complexidade da criação de tipos de dados compostos. Para usar esta API, o arquivo-fonte deve incluir:
#include "funcapi.h"
Existem duas maneiras de construir um valor de dados composto
(doravante chamado de “tupla”): pode ser construído
a partir de uma matriz de valores Datum, ou de uma matriz de
cadeia de caracteres C, que podem ser passadas
para as funções de conversão de entrada dos tipos de dados da
coluna da tupla.
Nos dois casos, é necessário primeiro obter ou construir um descritor
TupleDesc para a estrutura da tupla.
Ao trabalhar com Datums, se passa o TupleDesc
para o BlessTupleDesc, e depois se chama
heap_form_tuple para cada uma das linhas.
Ao trabalhar com cadeia de caracteres C,
se passa o TupleDesc para o
TupleDescGetAttInMetadata,
e depois se chama BuildTupleFromCStrings
para cada uma das linhas.
No caso da função retornar um conjunto de tuplas, todas as etapas
de configuração podem ser feitas uma vez durante a primeira
chamada da função.
Estão disponíveis várias funções auxiliares para configurar o
TupleDesc necessário.
A maneira recomendada de fazer isto, na maioria das funções que
retornam valores compostos, é chamar
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
passando a mesma estrutura fcinfo passada para
a própria função chamadora.
(Isto obviamente requer que sejam usadas as convenções de chamada
versão 1.)
resultTypeId pode ser especificado como
NULL ou como o endereço de uma variável local
para receber o tipo de resultado da função OID.
resultTupleDesc deve ser o endereço de uma
variável local TupleDesc.
Verifique se o resultado é TYPEFUNC_COMPOSITE;
se for, então resultTupleDesc foi preenchido
com o TupleDesc necessário.
(Se não for, pode ser relatado um erro nas linhas de
“função retornando registro chamada em contexto que não pode
aceitar o tipo de dados registro”.)
get_call_result_type pode resolver o tipo de dados
real do resultado de uma função polimórfica; portanto, é útil em
funções que retornam resultados polimórficos escalares,
e não apenas em funções que retornam tipos de dados compostos.
A saída de resultTypeId é útil principalmente
para funções que retornam escalares polimórficos.
A função get_call_result_type tem a irmã
gêmea get_expr_result_type, que pode ser
usada para resolver o tipo de dados de saída esperado para uma
chamada de função representada por uma árvore de expressão.
Isto pode ser usado ao tentar determinar o tipo de resultado de
fora da própria função.
Também existe a função get_func_result_type,
que pode ser usada quando apenas o OID da função está disponível.
No entanto, estas funções não conseguem lidar com funções
declaradas retornando record, e
get_func_result_type não pode resolver tipos
de dados polimórficos, então deve ser usado preferencialmente
get_call_result_type.
As funções mais antigas e em obsolescência
(deprecated) para obter
TupleDescs são
TupleDesc RelationNameGetTupleDesc(const char *relname)
para obter o TupleDesc para o tipo de dados
de linha de uma relação com nome, e
TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)
para obter o TupleDesc baseado em um
tipo de dados OID.
Isto pode ser usado para obter um TupleDesc
para um tipo de dados base ou composto.
No entanto, não funciona para uma função retornando
record, e não pode resolver
tipos de dados polimórficos.
Uma vez obtido o TupleDesc, chame
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
se planeja trabalhar com Datums, ou
AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)
se planeja trabalhar com cadeias de caracteres C.
Se estiver escrevendo uma função retornando conjunto, então poderá
salvar os resultados dessas funções na estrutura
FuncCallContext — use o campo
tuple_desc ou
attinmeta, respectivamente.
Ao trabalhar com Datums, use
HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)
para construir uma HeapTuple a partir dos
dados na forma de Datum.
Ao trabalhar com cadeia de caracteres C, use
HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
para construir uma HeapTuple a partir dos
dados na forma de cadeias de caracteres C.
values é uma matriz de cadeias de caracteres
C, uma para cada atributo da linha retornada.
Cada cadeia de caracteres C deve estar no formato
esperado pela função de entrada do tipo de dados do atributo.
Para retornar o valor nulo para um dos atributos, o ponteiro
correspondente na matriz valores deve ser
definido como NULL.
Esta função precisa ser chamada novamente para cada linha retornada.
Após construir a tupla de retorno da função, a tupla
deve ser convertida em Datum. Use
HeapTupleGetDatum(HeapTuple tuple)
para converter HeapTuple em um Datum válido.
Este Datum pode ser retornado diretamente se for
pretendido retornar apenas uma única linha, ou pode ser usado como
o valor retornado atual em uma função retornando conjunto.
É mostrado um exemplo na próxima seção.
As funções na linguagem C têm duas opções para
retornar conjuntos (múltiplas linhas).
Em um método, chamado modo ValuePerCall,
a função retornando de conjunto é chamada repetidamente
(passando os mesmos argumentos todas às vezes), e retorna uma nova
linha a cada chamada, até que não haja mais linhas para retornar,
sinalizando isto retornando NULL.
A função retornando conjunto (SRF) deve, portanto,
salvar condição de estado suficiente nas chamadas para lembrar o que
estava fazendo, e retornar o próximo item correto em cada chamada.
No outro método, chamado modo Materialize,
uma SRF preenche e retorna um objeto
tuplestore contendo todo o resultado;
então ocorre apenas uma chamada para todo o resultado, não havendo
necessidade de salvar o estado entre chamadas.
Ao usar o modo ValuePerCall, é importante lembrar não haver
garantia de que a consulta será executada até completar; ou seja,
devido a opções como LIMIT, o executor pode
parar de fazer chamadas para a função retornando conjunto antes
que todas as linhas tenham sido buscadas.
Isto significa não ser seguro deixar para realizar as atividades
de limpeza na última chamada, porque isto pode nunca acontecer.
É recomendável usar o modo Materialize para funções que precisam
de acesso a recursos externos, como descritores de arquivo.
O restante dessa seção documenta o conjunto de macros auxiliares
comumente usadas (embora não havendo necessidade de serem usadas)
para as SRFs usando o modo ValuePerCall.
Detalhes adicionais sobre o modo Materialize podem ser encontrados
no arquivo src/backend/utils/fmgr/README.
Além disso, os módulos contrib na distribuição
do código-fonte do PostgreSQL, contêm
muitos exemplos de SRFs usando os modos ValuePerCall e Materialize.
Para usar as macros de suporte ValuePerCall descritas aqui,
deve ser incluído o arquivo funcapi.h.
Estas macros trabalham com uma estrutura
FuncCallContext, que contém o estado
que precisa ser salvo nas chamadas.
Dentro do SRF que efetua a chamada, é usado
fcinfo->flinfo->fn_extra
para manter um ponteiro para FuncCallContext
entre as chamadas.
As macros preenchem automaticamente este campo no primeiro uso,
e esperam encontrar o mesmo ponteiro lá nos usos subsequentes.
typedef struct FuncCallContext
{
/*
* Número de chamadas anteriores
*
* call_cntr é inicializado como 0 por SRF_FIRSTCALL_INIT(),
* e incrementado toda vez que SRF_RETURN_NEXT() é chamado.
*/
uint64 call_cntr;
/*
* OPCIONAL: número máximo de chamadas
*
* max_calls está aqui apenas por conveniência, e configurá-lo é
* opcional. Se não for definido, devem ser fornecidos meios
* alternativos para saber quando a função está concluída.
*/
uint64 max_calls;
/*
* OPCIONAL: ponteiro para diversas informações de contexto fornecidas
* pelo usuário.
*
* user_fctx deve ser usado como ponteiro para os seus próprios
* dados, para reter informações de contexto arbitrárias entre
* as chamadas de sua função.
*/
void *user_fctx;
/*
* OPCIONAL: ponteiro para estrutura contendo metadados de entrada
* de tipo de dados de atributo
*
* attinmeta deve ser usado ao retornar tuplas (ou seja, tipos de
* dados compostos), não sendo usado para retornar tipos de dados base.
* Só é necessário se for pretendido usar BuildTupleFromCStrings()
* para criar a tupla de retorno.
*/
AttInMetadata *attinmeta;
/*
* Contexto de memória usado para estruturas que devem sobreviver
* entre várias chamadas
*
* multi_call_memory_ctx é definido por SRF_FIRSTCALL_INIT(),
* e usado por SRF_RETURN_DONE() para limpeza.
* É o contexto de memória mais apropriado para qualquer memória
* a ser reutilizada em várias chamadas da SRF.
*/
MemoryContext multi_call_memory_ctx;
/*
* OPCIONAL: ponteiro para a struct contendo a descrição da tupla
*
* tuple_desc deve ser usado ao retornar tuplas (ou seja, tipos de
* dados compostos), só sendo necessário se as tuplas forem construídas
* com heap_form_tuple(), em vez de BuildTupleFromCStrings().
* Note que o ponteiro TupleDesc armazenado aqui geralmente deve
* ter sido executado primeiro por meio de BlessTupleDesc().
*/
TupleDesc tuple_desc;
} FuncCallContext;
As macros a serem utilizadas por uma SRF utilizando esta infraestrutura são:
SRF_IS_FIRSTCALL()
Deve ser usada para determinar se a função está sendo chamada pela primeira vez, ou depois. Na primeira chamada (somente), deve ser chamada
SRF_FIRSTCALL_INIT()
para inicializar FuncCallContext.
Em toda chamada de função, incluindo a primeira, deve ser chamada
SRF_PERCALL_SETUP()
para configurar para usar FuncCallContext.
Se a função tiver dados para retornar na chamada atual, deve ser usado
SRF_RETURN_NEXT(funcctx, result)
para retorná-los para quem efetuou a chamada.
(result deve ser do tipo de dados Datum,
seja um único valor ou uma tupla preparada conforme descrito acima.)
Por fim, quando a função terminar de retornar dados, deve ser usado
SRF_RETURN_DONE(funcctx)
para limpar e terminar a SRF.
O contexto de memória corrente quando a SRF é
chamada é um contexto transitório, que será limpo entre as chamadas.
Isto significa não haver necessidade de chamar pfree
para liberar tudo que foi alocado usando palloc;
vai ser liberado de qualquer maneira.
Entretanto, se for desejado alocar estruturas de dados que persistam
entre chamadas, elas devem ser colocadas em outro lugar.
O contexto de memória referenciado por
multi_call_memory_ctx é um local adequado
para qualquer dado que precise sobreviver até que a
SRF termine sua execução.
Geralmente, isto significa que se deve alternar para
multi_call_memory_ctx ao fazer a
configuração da primeira chamada.
Deve ser usado funcctx->user_fctx para manter
um ponteiro para qualquer estrutura de dados de chamada cruzada.
(Os dados alocados em multi_call_memory_ctx
desaparecem automaticamente quando a consulta termina, então não é
necessário liberar estes dados manualmente, também.)
Quando os argumentos reais da função permanecem inalterados entre as
chamadas, se o valor dos argumentos forem lidos no contexto transitório
(o que é normalmente feito de forma transparente pela macro
PG_GETARG_),
então as cópias serão liberadas a cada ciclo.
Da mesma maneira, se forem mantidas referências a estes valores em
xxxuser_fctx, eles deverão ser copiados
para multi_call_memory_ctx após lidos,
ou se garantir que os valores são lidos apenas neste contexto.
Um exemplo de pseudocódigo completo se parece com o seguinte:
Datum
my_set_returning_function(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
Datum result;
declarações adicionais conforme necessário
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* O código de configuração de uma única vez aparece aqui: */
código do usuário
if retorna tipo de dados composto
construir TupleDesc, e talvez AttInMetadata
endif retorna tipo de dados composto
código do usuário
MemoryContextSwitchTo(oldcontext);
}
/* O código de configuração de cada vez aparece aqui: */
código do usuário
funcctx = SRF_PERCALL_SETUP();
código do usuário
/* Esta é apenas uma das maneiras para testar se terminamos: */
if (funcctx->call_cntr < funcctx->max_calls)
{
/* Aqui queremos retornar outro item: */
código do usuário
obter o Datum do resultado
SRF_RETURN_NEXT(funcctx, result);
}
else
{
/* Aqui concluímos o retorno dos itens, então basta informar esse fato. */
/* (Resista à tentação de inserir código de limpeza aqui.) */
SRF_RETURN_DONE(funcctx);
}
}
Um exemplo completo de uma SRF simples retornando tipo de dados composto se parece com:
PG_FUNCTION_INFO_V1(retcomposite);
Datum
retcomposite(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
int call_cntr;
int max_calls;
TupleDesc tupdesc;
AttInMetadata *attinmeta;
/* coisas feitas apenas na primeira chamada da função */
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
/* criar um contexto de função para persistência entre chamadas */
funcctx = SRF_FIRSTCALL_INIT();
/* alternar para o contexto de memória apropriado */
/* para múltiplas chamadas da função */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* número total de tuplas a serem retornadas */
funcctx->max_calls = PG_GETARG_INT32(0);
/* construir um descritor de tupla para o nosso tipo de resultado. */
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")));
/*
* gerar metadados de atributos necessários posteriormente para
* produzir tuplas a partir de cadeias de caracteres C puras
*/
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta;
MemoryContextSwitchTo(oldcontext);
}
/* tarefas realizadas em toda chamada da função */
funcctx = SRF_PERCALL_SETUP();
call_cntr = funcctx->call_cntr;
max_calls = funcctx->max_calls;
attinmeta = funcctx->attinmeta;
/* fazer quando ainda houver mais para retornar */
if (call_cntr < max_calls)
{
char **values;
HeapTuple tuple;
Datum result;
/*
* Preparar uma matriz de valores para construir a tupla retornada.
* Deverá ser uma matriz de cadeias de caracteres em C que
* será processada posteriormente pelas funções de entrada
* de tipo de dados.
*/
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));
/* construir a tupla */
tuple = BuildTupleFromCStrings(attinmeta, values);
/* transformar a tupla em datum */
result = HeapTupleGetDatum(tuple);
/* Limpeza (isso não é realmente necessário) */
pfree(values[0]);
pfree(values[1]);
pfree(values[2]);
pfree(values);
SRF_RETURN_NEXT(funcctx, result);
}
else /* fazer quando não houver mais nada a ser feito */
{
SRF_RETURN_DONE(funcctx);
}
}
Uma maneira de declarar esta função no 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;
Uma maneira diferente é usar parâmetros 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;
Note que neste método o tipo de dados de saída da função é
formalmente um tipo de dados record anônimo.
As funções na linguagem C podem ser declaradas
aceitando e retornando os tipos de dados polimórficos descritos em
Tipos de dados polimórficos.
Quando os argumentos ou tipos de dados retornados de uma função são
definidos como tipos polimórficos, o autor da função não pode saber
com antecedência com qual tipo de dados ela será chamada,
ou precisará retornar.
Existem duas rotinas fornecidas em fmgr.h para
permitir que uma função C, usando as convenções
versão 1, descubra os tipos de dados reais de seus argumentos,
e o tipo de dados de retorno esperado. Estas rotinas se chamam
get_fn_expr_rettype(FmgrInfo *flinfo) e
get_fn_expr_argtype(FmgrInfo *flinfo, int argnum).
Elas retornam o resultado, ou o OID do tipo de dados do argumento, ou
InvalidOid se a informação não estiver disponível.
A estrutura flinfo é normalmente acessada como
fcinfo->flinfo.
O parâmetro argnum tem como base zero.
A função get_call_result_type também pode ser
usada como alternativa para get_fn_expr_rettype.
Também existe a função get_fn_expr_variadic,
que pode ser usada para descobrir se os argumentos variádicos foram
unidos em uma matriz. Isto é útil principalmente para as funções
VARIADIC "any", uma vez que esta união sempre
ocorre para funções variádicas que usam tipos de dados matrizes comuns.
Por exemplo, supondo que queiramos escrever uma função para aceitar um único elemento de qualquer tipo de dados e retornar uma matriz unidimensional desse tipo de dados:
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");
/* obter o elemento fornecido, tomando cuidado caso ele seja NULL. */
isnull = PG_ARGISNULL(0);
if (isnull)
element = (Datum) 0;
else
element = PG_GETARG_DATUM(0);
/* temos uma dimensão */
ndims = 1;
/* e um elemento */
dims[0] = 1;
/* e o limite inferior é 1 */
lbs[0] = 1;
/* obter as informações necessárias sobre o tipo de dados do elemento. */
get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);
/* agora construir a matriz */
result = construct_md_array(&element, &isnull, ndims, dims, lbs,
element_type, typlen, typbyval, typalign);
PG_RETURN_ARRAYTYPE_P(result);
}
O seguinte comando declara a função make_array
no SQL:
CREATE FUNCTION make_array(anyelement) RETURNS anyarray
AS 'DIRECTORY/funcs', 'make_array'
LANGUAGE C IMMUTABLE;
Existe uma variante de polimorfismo que só está disponível para
funções na linguagem C: elas podem ser
declaradas aceitando parâmetros do tipo de dados "any".
(Note que este nome de tipo de dados deve estar entre aspas, porque
também é uma palavra reservada do SQL.)
Funciona como anyelement, exceto por não restringir os
diferentes argumentos "any" a serem todos do
mesmo tipo de dados, nem ajuda a determinar o tipo de dados do
resultado da função.
Uma função na linguagem C também pode declarar
seu parâmetro final como VARIADIC "any".
Isto corresponderá a um ou mais argumentos reais de qualquer
tipo de dados (não necessariamente do mesmo tipo de dados).
Estes argumentos não serão reunidos em uma
matriz, como acontece com funções variádicas normais;
eles apenas serão passados para a função em separado.
A macro PG_NARGS(), e os métodos descritos
acima, devem ser usados para determinar o número de argumentos
reais e seus tipos de dados ao usar este recurso.
Além disso, os usuários dessa função podem querer usar a palavra-chave
VARIADIC em sua chamada de função, com a
expectativa de que a função trate os elementos da matriz como
argumentos separados.
A própria função deve implementar este comportamento, se desejado,
após usar get_fn_expr_variadic para
detectar qual argumento real foi marcado com
VARIADIC.
Os módulos (add-ins) podem
reservar memória compartilhada na inicialização do servidor.
Para fazer isto, a biblioteca compartilhada do módulo deve ser
pré-carregada, especificando-a em
shared_preload_libraries.
A biblioteca compartilhada também deve registrar um
shmem_request_hook em sua função
_PG_init.
Este shmem_request_hook pode reservar memória
compartilhada chamando:
void RequestAddinShmemSpace(Size size)
Cada processo servidor em segundo plano (backend) deve obter um ponteiro para a memória compartilhada reservada chamando:
void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
Se esta função definir foundPtr como
false, quem chama deverá proceder à
inicialização do conteúdo da memória compartilhada reservada.
Se foundPtr for definido como
true, a memória compartilhada já foi
inicializada por outro processo servidor em segundo plano
e quem chama não irá precisar inicializá-la.
further.
Para evitar condições de disputa, cada processo servidor em
segundo plano deve usar o LWLock
AddinShmemInitLock ao inicializar sua
alocação de memória compartilhada, como mostrado aqui:
static mystruct *ptr = NULL;
bool found;
LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
ptr = ShmemInitStruct("my struct name", size, &found);
if (!found)
{
... Inicializar o conteúdo da memória compartilhada. ...
ptr->locks = GetNamedLWLockTranche("my tranche name");
}
LWLockRelease(AddinShmemInitLock);
shmem_startup_hook oferece um local conveniente
para o código de inicialização, mas não é estritamente necessário
que todo este código seja colocado neste gancho.
No Windows (e em qualquer
outro lugar onde EXEC_BACKEND esteja definido),
cada processo servidor em segundo plano executa o
shmem_startup_hook registrado logo após se
conectar à memória compartilhada, para que os módulos ainda possam
adquirir AddinShmemInitLock dentro deste
gancho, como mostrado no exemplo acima.
Nas outras plataformas, apenas o processo
postmaster executa o
shmem_startup_hook, e cada processo servidor
em segundo plano herda automaticamente os ponteiros para a
memória compartilhada.
Pode ser encontrado um exemplo de
shmem_request_hook e
shmem_startup_hook em
contrib/pg_stat_statements/pg_stat_statements.c
na distribuição do código-fonte do
PostgreSQL.
Existe outro método, mais flexível, para reservar memória
compartilhada, que pode ser feito após a inicialização do
servidor e fora do shmem_request_hook.
Para fazer isto, cada processo servidor em segundo plano que
utilizará a memória compartilhada deve obter um ponteiro para
ela chamando o seguinte comando:
void *GetNamedDSMSegment(const char *name, size_t size,
void (*init_callback) (void *ptr),
bool *found)
Se ainda não existir um segmento de memória compartilhada dinâmica
com o nome fornecido, esta função o alocará e o inicializará com a
função de retorno de chamada init_callback
fornecida.
Se o segmento já tiver sido alocado e inicializado por outro
processo servidor em segundo plano, esta função simplesmente
irá anexar o segmento de memória compartilhada dinâmica existente
ao processo servidor em segundo plano corrente.
Ao contrário da memória compartilhada reservada na inicialização
do servidor, não é necessário adquirir
AddinShmemInitLock ou tomar qualquer outra
ação para evitar condições de disputa ao reservar memória
compartilhada com GetNamedDSMSegment.
Esta função garante que apenas um processo servidor em segundo
plano aloque e inicialize o segmento e que todos os outros
processos servidores em segundo plano recebam um ponteiro para
o segmento totalmente alocado e inicializado.
Um exemplo completo de uso de GetNamedDSMSegment
pode ser encontrado no arquivo
src/test/modules/test_dsm_registry/test_dsm_registry.c
na distribuição do código-fonte do PostgreSQL.
Os módulos podem reservar LWLocks na inicialização do servidor.
Assim como a memória compartilhada reservada na inicialização do
servidor, a biblioteca compartilhada do módulo deve ser
pré-carregada, especificando-a em
shared_preload_libraries,
e a biblioteca compartilhada deve registrar um
shmem_request_hook em sua função
_PG_init.
Este shmem_request_hook pode reservar LWLocks
chamando:
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
Isto garante que uma matriz de num_lwlocks
LWLocks esteja disponível sob o nome tranche_name.
Um ponteiro para esta matriz pode ser obtido chamando:
LWLockPadded *GetNamedLWLockTranche(const char *tranche_name)
Existe outro método, mais flexível, para obter LWLocks que pode
ser feito após a inicialização do servidor e fora de um
shmem_request_hook.
Para isto, primeiro deve ser alocado um
tranche_id chamando:
int LWLockNewTrancheId(void)
Em seguida, inicializar cada LWLock, passando o novo
tranche_id como argumento:
void LWLockInitialize(LWLock *lock, int tranche_id)
Semelhante à memória compartilhada, cada processo servidor em
segundo plano deve garantir que apenas um processo aloque um novo
tranche_id e inicialize cada novo LWLock.
Uma maneira de fazer isto é chamar estas funções apenas no seu
código de inicialização de memória compartilhada com a função
AddinShmemInitLock sendo mantida exclusivamente.
Se for usado GetNamedDSMSegment, chamar estas
funções na função de retorno de chamada
init_callback é suficiente para evitar
condições de disputa.
Por fim, cada processo servidor em segundo plano que utiliza o
tranche_id deve associá-lo a um
tranche_name chamando:
void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
Um exemplo completo de uso de
LWLockNewTrancheId,
LWLockInitialize e
LWLockRegisterTranche
pode ser encontrado no arquivo
contrib/pg_prewarm/autoprewarm.c
na distribuição do código-fonte do
PostgreSQL.
Os módulos podem definir eventos de espera personalizados no
tipo de evento de espera Extension chamando:
uint32 WaitEventExtensionNew(const char *wait_event_name)
O evento de espera está associado a uma cadeia de caracteres
personalizada visível para o usuário.
Pode ser encontrado um exemplo no diretório
src/test/modules/worker_spi
na distribuição do código-fonte do
PostgreSQL.
Podem ser vistos eventos de espera personalizados em pg_stat_activity:
=# SELECT wait_event_type, wait_event FROM pg_stat_activity
WHERE backend_type ~ 'worker_spi';
wait_event_type | wait_event
-----------------+---------------
Extension | WorkerSpiMain
(1 linha)
Um ponto de injeção com um determinado name
é declarado usando a macro:
INJECTION_POINT(name, arg);
Já existem alguns pontos de injeção declarados em locais
estratégicos dentro do código do servidor.
Após adicionar um novo ponto de injeção, o código precisa ser
compilado para que este ponto de injeção esteja disponível em binário.
Módulos escritos na linguagem C podem declarar
pontos de injeção em seu próprio código usando a mesma macro.
Os nomes dos pontos de injeção devem usar letras minúsculas,
com os termos separados por hifens.
arg é um valor de argumento opcional fornecido
ao retorno de chamada em tempo de execução.
A execução de um ponto de injeção pode exigir a alocação de uma pequena quantidade de memória, o que pode falhar. Se houver necessidade de um ponto de injeção em uma seção crítica onde alocações dinâmicas não são permitidas, pode-se usar uma abordagem em duas etapas com as seguintes macros:
INJECTION_POINT_LOAD(name); INJECTION_POINT_CACHED(name, arg);
Antes de entrar na seção crítica, deve-se chamar
INJECTION_POINT_LOAD.
Isto verifica o estado da memória compartilhada e carrega o retorno
de chamada na memória privada do processo servidor em segundo plano
se estiver ativo.
Dentro da seção crítica, deve-se usar
INJECTION_POINT_CACHED para executar o
retorno de chamada.
Os módulos podem associar funções de retorno de chamada a um ponto de injeção já declarado, chamando:
extern void InjectionPointAttach(const char *name,
const char *library,
const char *function,
const void *private_data,
int private_data_size);
name é o nome do ponto de injeção, que, quando
alcançado durante a execução, executará a function
carregada da library.
private_data é uma área privada de dados de
tamanho private_data_size fornecida como
argumento para a função de retorno de chamada quando executada.
Aqui está um exemplo de retorno de chamada para
InjectionPointCallback:
static void
custom_injection_callback(const char *name,
const void *private_data,
void *arg)
{
uint32 wait_event_info = WaitEventInjectionPointNew(name);
pgstat_report_wait_start(wait_event_info);
elog(NOTICE, "%s: executed custom callback", name);
pgstat_report_wait_end();
}
Esta função de retorno escreve uma mensagem no registro de erros
do servidor com a severidade NOTICE, mas as
funções de retorno podem implementar uma lógica mais complexa.
Uma forma alternativa de definir a ação a ser tomada quando um
ponto de injeção for atingido é adicionar o código de teste junto
com o código-fonte normal.
Isto pode ser útil se a ação, por exemplo, depender de variáveis
locais que não são acessíveis aos módulos carregados.
A macro IS_INJECTION_POINT_ATTACHED
pode então ser usada para verificar se um ponto de injeção está
conectado como, por exemplo:
#ifdef USE_INJECTION_POINTS
if (IS_INJECTION_POINT_ATTACHED("before-foobar"))
{
/* change a local variable if injection point is attached */
local_var = 123;
/* also execute the callback */
INJECTION_POINT_CACHED("before-foobar", NULL);
}
#endif
Note que a função de retorno de chamada associada ao ponto de
injeção não será executada pela macro
IS_INJECTION_POINT_ATTACHED .
Se for desejado executar a chamada de retorno, também deverá
ser chamado INJECTION_POINT_CACHED,
como no exemplo acima.
Opcionalmente, é possível desanexar um ponto de injeção chamando:
extern bool InjectionPointDetach(const char *name);
No caso de sucesso é retornado true, e
false caso contrário.
Uma chamada de retorno associada a um ponto de injeção está
disponível em todos os processos servidores em segundo plano,
incluindo os processos servidores em segundo plano iniciados
após a chamada de InjectionPointAttach.
Permanecerá conectado enquanto o servidor estiver em execução,
ou até que o ponto de injeção seja desconectado usando
InjectionPointDetach.
Pode ser encontrado um exemplo no diretório
src/test/modules/injection_points na
distribuição do código-fonte do PostgreSQL.
A ativação dos pontos de injeção requer
--enable-injection-points com
configure, ou -Dinjection_points=true
com Meson.
É possível que módulos escritos na linguagem C utilizem tipos personalizados de estatísticas cumulativas registradas no >Configuração de coleta de estatísticas.
Primeiro, deve ser definido um PgStat_KindInfo
que inclua todas as informações relacionadas ao tipo personalizado
registrado. Por exemplo:
static const PgStat_KindInfo custom_stats = {
.name = "custom_stats",
.fixed_amount = false,
.shared_size = sizeof(PgStatShared_Custom),
.shared_data_off = offsetof(PgStatShared_Custom, stats),
.shared_data_len = sizeof(((PgStatShared_Custom *) 0)->stats),
.pending_size = sizeof(PgStat_StatCustomEntry),
}
Então, cada processo servidor em segundo plano que precisar usar
este tipo personalizado deverá registrá-lo com
pgstat_register_kind e um ID exclusivo usado
para armazenar as entradas relacionadas a este tipo de estatística:
extern PgStat_Kind pgstat_register_kind(PgStat_Kind kind,
const PgStat_KindInfo *kind_info);
Ao desenvolver uma nova extensão, deve ser usado
PGSTAT_KIND_EXPERIMENTAL para
kind.
Quando o módulo estiver pronto para ser liberado para os usuários,
deve ser reservado um ID de tipo na página
Custom Cumulative Statistics.
Os detalhes da API para
PgStat_KindInfo podem ser encontrados no arquivo
src/include/utils/pgstat_internal.h da
distribuição do código-fonte do PostgreSQL.
O tipo de estatística registrada está associado a um nome e a um ID
exclusivo compartilhado em todo o servidor na memória compartilhada.
Cada processo servidor em segundo plano que utiliza um tipo
personalizado de estatísticas mantém um
cache local armazenando as
informações de cada PgStat_KindInfo personalizado.
O módulo de extensão que implementa o tipo de estatísticas cumulativas personalizadas deve ser colocado em shared_preload_libraries para que seja carregado na inicialização do PostgreSQL.
Um exemplo que descreve como registrar e usar estatísticas
personalizadas pode ser encontrado no diretório
src/test/modules/injection_points da
distribuição do código-fonte do PostgreSQL.
Embora o servidor do PostgreSQL seja escrito em C, é possível escrever extensões em C++ se estas diretrizes forem seguidas:
Todas as funções acessadas pelo servidor devem apresentar uma
interface C para o servidor; estas funções
C podem então chamar funções
C++.
Por exemplo, a vinculação extern C é
necessária para funções acessadas pelo servidor.
Isto também é necessário para quaisquer funções passadas como
ponteiros entre o servidor e o código C++.
Libere a memória usando o método de liberação apropriado.
Por exemplo, a maioria da memória do servidor é alocada usando
a função palloc(), então deve ser usada
a função pfree() para liberá-la.
Nesses casos, usar a função delete do
C++ vai falhar.
Evite a propagação das exceções para o código C
(use um bloco “pega-tudo”
(catch-all) no nível superior de
todas as funções extern C).
Isto é necessário mesmo se o código C++ não
lançar nenhuma exceção explicitamente, porque eventos como f
alta de memória ainda podem lançar exceções.
Toda exceção deve ser capturada, e os erros apropriados devem
ser passados de volta para a interface C.
Se possível, compile o código C++ com
a opção -fno-exceptions para eliminar
inteiramente as exceções; nesses casos, deve ser verificado se há
falhas no código C++, por exemplo, verificando
se há NULL retornado por new().
Se forem chamadas funções do servidor a partir do código
C++, deve-se certificar de que a pilha de
chamadas C++ contenha apenas estruturas de
dados antigas simples (POD).
Isto é necessário, porque os erros do servidor geram um salto
via longjmp() que não desfaz corretamente a
pilha de chamadas C++ com objetos não-POD.
Em resumo, é melhor colocar o código C++ atrás
de uma parede de funções extern C fazendo a
interface com o servidor, e evitar vazamento de exceção, memória
e pilha de chamadas.
[115]
stdint.h é um arquivo de cabeçalho da
biblioteca padrão C, introduzido na seção 7.18
da biblioteca padrão C99, visando permitir que
os programadores escrevam código mais portável, fornecendo um
conjunto de typedefs especificando tipos de dados
inteiros de largura exata, juntamente com os valores mínimo e
máximo permitidos definidos para cada tipo de dados, usando macros.
C Programming/stdint.h (N. T.)
[116]
Presente no diretório do Tutorial
(src/tutorial)
da distribuição do código-fonte do
PostgreSQL, que contém o código para
os exemplos usados nesta seção (N. T.)