Использование функций на языке C

В ADPG можно использовать язык C (или язык, совместимый с C, например C++) для написания пользовательских функций. Главной особенностью этих функций является то, что они компилируются в динамически загружаемые объекты (также называемые разделяемыми библиотеками (shared libraries)), которые сервер загружает по требованию. Функция динамической загрузки отличает функции языка C от внутренних функций — фактические соглашения о кодировании для них по существу одинаковы. Следовательно, стандартная библиотека внутренних функций является богатым источником примеров кода для пользовательских функций C.

В настоящее время для функций C используется только одно соглашение о вызовах — версия 1. Поддержка этого соглашения обозначается объявлением функции с макросом PG_FUNCTION_INFO_V1, как показано ниже.

Динамическая загрузка

При первом вызове в сессии пользовательской функции, находящейся в загружаемом объектном файле, динамический загрузчик загружает этот файл в память, чтобы можно было вызвать функцию. В команде CREATE FUNCTION необходимо указать две опции для функции: имя загружаемого объектного файла и имя функции С из этого файла, которую нужно вызвать. Если имя функции C не указано явно, предполагается, что оно совпадает с именем функции SQL.

Следующий алгоритм используется для поиска разделяемого объектного файла по имени, указанному в команде CREATE FUNCTION:

  • Если имя является абсолютным путем, загружается данный файл.

  • Если имя начинается со строки $libdir, эта часть пути заменяется путем к каталогу библиотеки пакетов PostgreSQL, который определяется во время сборки.

  • Если имя не содержит пути к директории, файл ищется по пути, указанному в переменной конфигурации dynamic_library_path.

  • В противном случае (файл не найден по описанному алгоритму и не содержит абсолютный путь к каталогу) динамический загрузчик попытается принять имя как есть, что, скорее всего, не позволит загрузить файл. Ненадежно полагаться на текущий рабочий каталог.

Если описанные шаги не сработают, к заданному имени добавляется расширение имени файла общей библиотеки, специфичное для платформы (часто .so), и указанные выше шаги повторяются снова. Если это не удастся, загрузка не состоится.

Путь к библиотеке по умолчанию — /usr/lib/adpg16/lib. Чтобы проверить это, выполните следующие команды psql:

SHOW dynamic_library_path;
 dynamic_library_path
----------------------
 $libdir
(1 row)
SELECT setting FROM pg_config WHERE name='PKGLIBDIR';
       setting
---------------------
 /usr/lib/adpg16/lib

Кроме того, вы можете определить, какой путь содержит $libdir, с помощью команды pg_config --pkglibdir. Для этого сначала необходимо установить пакет adpg16-devel, как описано ниже.

Пользователь, от имени которого запущен ADPG-сервер, должен иметь возможность пройти путь к загружаемому объектному файлу. Распространенной ошибкой является то, что файл или каталог более высокого уровня становится недоступным для чтения и/или выполнения этим пользователем.

Чтобы гарантировать, что динамически загружаемый объектный файл не будет загружен на несовместимый сервер, ADPG/PostgreSQL проверяет, содержит ли файл "магический блок" ("magic block") с соответствующим содержимым. Это позволяет серверу обнаруживать несовместимости, например код, скомпилированный для другой мажорной версии ADPG/PostgreSQL. Чтобы включить магический блок, напишите в одном из исходных файлов модуля после заголовка #include "fmgr.h":

PG_MODULE_MAGIC;

После первого использования динамически загружаемый объектный файл сохраняется в памяти. В той же сессии будущие вызовы функций из этого файла будут нести лишь небольшие накладные расходы на поиск в таблице символов. Если необходимо принудительно перезагрузить объектный файл, например, после его перекомпиляции, начните новую сессию.

Также динамически загружаемый файл может содержать функции инициализации и финализации. Если файл содержит функцию с именем _PG_init, эта функция будет вызвана сразу после загрузки файла. Функция _PG_init не получает параметров и должна возвращать void. В настоящее время невозможно выгрузить динамически загружаемый файл, поэтому функция финализации не используется.

Пример создания функции

ADPG/PostgreSQL не компилирует функции C автоматически. Объектный файл должен быть скомпилирован до того, как на него будет сделана ссылка в команде CREATE FUNCTION.

Создание разделяемых библиотек в целом аналогично созданию исполняемых файлов: сначала исходные файлы компилируются в объектные файлы, затем объектные файлы компонуются друг с другом. Объектные файлы должны создаваться так, чтобы они содержали позиционно-независимый код (PIC), что означает, что они могут быть помещены в произвольное место в памяти при загрузке исполняемым файлом.

Если в вашей системе нет компилятора C, вы можете установить GNU Compiler Collection (GCC), как описано ниже.

  • YUM

  • APT

Репозитории CentOS по умолчанию содержат группу пакетов под названием Development Tools, которая содержит компилятор GCC, а также множество библиотек и других утилит, необходимых для компиляции программного обеспечения.

Чтобы установить Development Tools, включая компилятор GCC, выполните следующую команду:

$ sudo yum group install "Development Tools"

Чтобы убедиться, что компилятор GCC успешно установлен, используйте следующую команду, которая выводит версию GCC:

$ gcc --version

Установите пакет adpg16-devel, содержащий файлы, предназначенные для серверного программирования ADPG/PostgreSQL:

$ sudo yum install adpg16-devel

Чтобы установить компилятор GCC, выполните следующую команду:

$ sudo apt install gcc

В качестве альтернативы, вы можете установить пакет build-essential, содержащий дополнительные утилиты, используемые с GCC:

$ sudo apt install build-essential

Чтобы убедиться, что компилятор GCC успешно установлен, используйте следующую команду, которая выводит версию GCC:

$ gcc --version

Установите пакет adpg16-devel, содержащий файлы, предназначенные для серверного программирования ADPG/PostgreSQL:

$ sudo apt-get install adpg16-devel

Создайте функцию C, которая возвращает квадрат числа от 1 до переданного значения параметра. Обратите внимание, что всегда необходимо сначала включать postgres.h в любой исходный файл серверного кода. Файл postgres.h содержит определения для множества необходимых типов и функций, fmgr.h включает интерфейсы менеджера функций (PG_FUNCTION_ARGS и т.д.), funcapi.h требуется для того, чтобы возвращать набор строк.

По умолчанию postgres.h и другие файлы заголовков расположены по следующему пути: /usr/lib/adpg16/include/server. Вы можете поместить создаваемый файл в эту директорию или указать путь к файлам заголовков (#include "postgres.h" и другим) в соответствии с путем к создаваемому файлу.

#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(function_test);

Datum
function_test(PG_FUNCTION_ARGS)
{
    FuncCallContext *function_call_context;
    MemoryContext old_context;
    if (SRF_IS_FIRSTCALL()) {
    function_call_context = SRF_FIRSTCALL_INIT();
    old_context = MemoryContextSwitchTo(function_call_context->multi_call_memory_ctx);
    function_call_context->max_calls = PG_GETARG_INT32(0);
    MemoryContextSwitchTo(old_context);
    }
    function_call_context = SRF_PERCALL_SETUP();
    if (function_call_context->call_cntr < function_call_context->max_calls) {
        SRF_RETURN_NEXT(function_call_context, pow(Int32GetDatum(function_call_context->call_cntr),2));
    } else {
        SRF_RETURN_DONE(function_call_context);
    }
}

Укажите имя файла, содержащего функцию, как foo.c.

Задайте переменную окружения C_INCLUDE_PATH, которая должна содержать путь к файлам заголовков, подключаемых через #include, для компилятора GCC:

$ export C_INCLUDE_PATH=$C_INCLUDE_PATH:/lib/adpg16/include/server

В этом примере исходный код находится в файле foo.c, и создается общая библиотека с именем foo.so. Промежуточный объектный файл называется foo.o. Разделяемая библиотека может содержать более одного объектного файла, но в примере используется один.

$ cc -fPIC -c foo.c
$ cc -shared -o foo.so foo.o

За дополнительной информацией обратитесь к статье Compiling and linking dynamically-loaded functions.

Создайте функцию в PostgreSQL:

CREATE OR REPLACE FUNCTION
    function_test(integer) RETURNS setof int4 AS
    '/usr/lib/adpg16/include/server/foo.so', 'function_test'
LANGUAGE C
STRICT;

Протестируйте функцию:

SELECT function_test(4);

Результат:

 function_test
---------------
             1
             4
             9
            16

Базовые типы в функциях языка C

ADPG/PostgreSQL рассматривает базовый тип как "объект памяти". Пользовательские функции определяют способ работы PostgreSQL с ним. То есть ADPG/PostgreSQL только сохраняет и извлекает данные с диска и использует определяемые пользователем функции для ввода, обработки и вывода данных.

Базовые типы могут иметь один из трех внутренних форматов:

  • передается по значению, имеет фиксированную длину;

  • передается по ссылке, имеет фиксированную длину;

  • передается по ссылке, имеет переменную длину.

Типы по значению могут иметь длину только 1, 2 или 4 байта (также 8 байт, если sizeof(Datum) равен 8 на текущем компьютере). Следует быть осторожным при определении типов таким образом, чтобы они имели одинаковый размер (в байтах) на всех архитектурах. Например, тип long составляет 4 байта на некоторых машинах и 8 байтов на других, тогда как тип int составляет 4 байта на большинстве машин Unix. Разумной реализацией типа int4 на машинах Unix может быть:

/* 4-байтовое целое число, передаваемое по значению */
typedef int int4;

Фактический код в PostgreSQL определяет этот тип как int32, поскольку в C существует соглашение, согласно которому intXX означает XX бит. Поэтому обратите внимание, что тип C int8 имеет размер 1 байт. Тип SQL int8 называется int64 в C.

Типы фиксированной длины любого размера могут передаваться по ссылке. Например, следующая реализация типа PostgreSQL:

/* 16-байтовая структура, передаваемая по ссылке */
typedef struct
{
    double  x, y;
} Point;

Можно использовать только указатели на такие типы при передаче их в/из функций PostgreSQL. Чтобы вернуть значение такого типа, выделите память с помощью функции palloc, заполните выделенную память и верните на нее указатель. Кроме того, если вы просто хотите вернуть то же значение, что и один из входных аргументов, верните указатель на входное значение.

Все типы переменной длины также должны передаваться по ссылке. Такие типы должны начинаться с обязательного поля длины размером 4 байта, которое устанавливается макросом SET_VARSIZE. Никогда не устанавливайте это поле вручную. Все данные, которые будут храниться в этом типе, должны располагаться в памяти сразу после этого поля длины.

Никогда не изменяйте содержимое входного значения, передаваемого по ссылке. Эта операция может привести к повреждению данных на диске, поскольку указатель может указывать непосредственно на буфер диска.

В качестве примера можно определить тип text следующим образом:

typedef struct {
    int32 length;
    char data[FLEXIBLE_ARRAY_MEMBER];
} text;

[FLEXIBLE_ARRAY_MEMBER] означает, что фактическая длина данных не указана в этом объявлении.

При работе с типами переменной длины вы должны быть осторожны, чтобы выделить правильный объем памяти и правильно установить поле длины. Например, чтобы сохранить 40 байт в текстовой структуре, используйте такой фрагмент кода:

#include "postgres.h"
...
char buffer[40]; /* источник данных */
...
text *destination = (text *) palloc(VARHDRSZ + 40);
SET_VARSIZE(destination, VARHDRSZ + 40);
memcpy(destination->data, buffer, 40);
...

VARHDRSZ аналогичен sizeof(int32), но для обозначения размера служебных данных для типа переменной длины считается хорошим стилем использовать макрос VARHDRSZ.

В таблице ниже перечислены типы C, соответствующие встроенным типам данных SQL в ADPG/PostgreSQL. Столбец Определен в файле содержит имя файла заголовка, который необходимо включить с помощью include для получения определения типа. Как упоминалось выше, в любой исходный файл серверного кода необходимо сначала включить postgres.h.

Эквивалентные типы C для встроенных типов SQL
Тип SQL Тип C Определен в файле

boolean

bool

postgres.h (maybe compiler built-in)

box

BOX*

utils/geo_decls.h

bytea

bytea*

postgres.h

"char"

char

(compiler built-in)

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

Соглашение о вызовах версии 1

Соглашение о вызовах версии 1 полагается на макросы для выполнения операций, связанных с передачей аргументов и результатов. По соглашению версии 1 функция на C должна всегда определяться следующим образом:

Datum <имя_функции>(PG_FUNCTION_ARGS)

Кроме того, непосредственно перед самой функцией необходимо написать следующий вызов макроса:

PG_FUNCTION_INFO_V1(<имя_функции>);

Каждый аргумент выбирается макросом PG_GETARG_xxx(), который соответствует типу данных аргумента (xxx — тип данных). В нестрогих функциях этому вызову должна предшествовать проверка на NULL в аргументе с использованием PG_ARGISNULL(). Результат возвращается макросом PG_RETURN_xxx() для возвращаемого типа. PG_GETARG_xxx() принимает в качестве параметра номер выбираемого аргумента функции (нумерация начинается с 0). PG_RETURN_xxx() принимает фактическое значение, которое нужно возвратить.

Примеры:

/* Файл funcs.c, скомпилированный в funcs.so */
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
#include "varatt.h"

PG_MODULE_MAGIC;

/* по значению */

PG_FUNCTION_INFO_V1(add_one);

Datum
add_one(PG_FUNCTION_ARGS)
{
    int32   arg = PG_GETARG_INT32(0);

    PG_RETURN_INT32(arg + 1);
}

/* по ссылке, фиксированной длины */

PG_FUNCTION_INFO_V1(add_one_float8);

Datum
add_one_float8(PG_FUNCTION_ARGS)
{
    float8  arg = PG_GETARG_FLOAT8(0);

    PG_RETURN_FLOAT8(arg + 1.0);
}

/* по ссылке, переменной длины */

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);

/* VARSIZE_ANY_EXHDR  -- размер структуры в байтах, минус VARHDRSZ или VARHDRSZ_SHORT её заголовка. */

    int32 new_text_size = arg1_size + arg2_size + VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);
    /*
     * VARDATA -- указатель на область данных новой структуры.  Источник
     * может быть short datum, поэтому его данные извлекаются через VARDATA_ANY.
     */

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

После того как код был подготовлен в файле funcs.c, скомпилирован в объект разделяемой библиотеки, как описано в разделе Пример создания функции, и помещен в каталог, указанный как путь к библиотеке по умолчанию, вы можете определить функции в ADPG/PostgreSQL с помощью следующих команд:

CREATE FUNCTION add_one(integer) RETURNS integer
AS 'funcs', 'add_one'
LANGUAGE C STRICT;

-- Перегрузка для SQL-функции "add_one"

CREATE FUNCTION add_one(double precision) RETURNS double precision
AS 'funcs', 'add_one_float8'
LANGUAGE C STRICT;

CREATE FUNCTION concat_text(text, text) RETURNS text
AS 'funcs', 'concat_text'
LANGUAGE C STRICT;

В качестве альтернативы, можно поместить funcs.so в любой другой каталог и указать полный путь к этому файлу вместо funcs. Убедитесь, что пользователь, от имени которого работает ADPG-сервер, может выполнить этот файл.

Обратите внимание, что мы указали функции как STRICT, что означает, что система должна вернуть результат NULL, если какое-либо входное значение равно NULL. Это позволяет избежать проверки аргументов на NULL в коде функции. Без STRICT следует явно проверять значения NULL, используя PG_ARGISNULL(). Например:

 isnull = PG_ARGISNULL(0);
    if (isnull)
        element = (Datum) 0;
    else
        element = PG_GETARG_DATUM(0);

Основные правила написания кода

Основные правила написания и построения функций C следующие:

  • Добавьте "магический блок" для разделяемой библиотеки — PG_MODULE_MAGIC;.

  • При выделении памяти используйте функции PostgreSQL palloc и pfree вместо соответствующих функций библиотеки C malloc и free. Память, выделенная palloc, будет автоматически освобождаться в конце каждой транзакции, предотвращая утечки памяти.

  • Всегда обнуляйте байты структур, используя memset (или сразу выделяйте память функцией palloc0). Даже если вы назначаете значения каждому полю структуры, могут оставаться байты выравнивания, содержащие случайные значения. Без этого трудно поддерживать хеш-индексы или хеш-соединения, поскольку для вычисления хеша придется выбирать только значимые биты структуры данных. Планировщик также иногда полагается на побитовое сравнение констант, поэтому можно получить нежелательные результаты планирования, если логически эквивалентные значения не равны побитово.

  • Большинство внутренних типов PostgreSQL объявлены в postgres.h, а интерфейсы менеджера функций (PG_FUNCTION_ARGS и т.д.) находятся в fmgr.h. Поэтому необходимо подключать с помощью include в файлы кода как минимум эти два файла. Как упоминалось выше, по умолчанию файлы заголовков сервера ADPG расположены по следующему пути: /usr/lib/adpg16/include/server. По соображениям портируемости лучше всего сначала подключать postgres.h перед любыми другими системными или пользовательскими файлами заголовков. При подключении postgres.h также подключатся elog.h и palloc.h.

  • Имена символов, определенные в объектных файлах, не должны конфликтовать друг с другом или с именами символов, определенных в исполняемых файлах сервера ADPG. Если вы получите сообщения об ошибках, связанные с такими конфликтами, следует переименовать свои функции или переменные.

Аргументы составного типа

Составные типы не имеют фиксированного макета данных, как структуры C. Экземпляры составного типа могут содержать поля NULL. Кроме того, составные типы, являющиеся частью иерархии наследования, могут иметь поля, отличные от полей других членов той же иерархии наследования. Поэтому ADPG/PostgreSQL предоставляет функциональный интерфейс для доступа к полям составных типов из C.

В качестве примера рассмотрим функцию, которая возвращает TRUE, если книга написана автором с указанным идентификатором:

SELECT title, books_by_author_id(book, 1) AS is_author
FROM book WHERE is_author = TRUE;
/* Файл foo2.c, скомпилированный в foo2.so */
#include "postgres.h"
#include "executor/executor.h"  /* для GetAttributeByName() */

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(books_by_author_id);

Datum
books_by_author_id(PG_FUNCTION_ARGS)
{
    HeapTupleHeader  t = PG_GETARG_HEAPTUPLEHEADER(0);
    int32            author_id = PG_GETARG_INT32(1);
    bool isnull;
    Datum author;

    author = GetAttributeByName(t, "author_id", &isnull);
    if (isnull)
        PG_RETURN_BOOL(false);

    PG_RETURN_BOOL(DatumGetInt32(author) == author_id);
}
CREATE FUNCTION books_by_author_id(book, integer) RETURNS boolean
    AS 'foo2', 'books_by_author_id'
    LANGUAGE C STRICT;

SELECT title, books_by_author_id(book, 1) AS is_author FROM book;

Результат:

         title         | is_author
-----------------------+-----------
 The Great Gatsby      | f
 The Lord of the Rings | f
 1984                  | f
 Animal Farm           | f
 Mrs. Dalloway         | t
 To the Lighthouse     | t
 To Kill a Mockingbird | f

GetAttributeByName — это системная функция PostgreSQL, которая возвращает атрибуты указанной строки. У нее есть три аргумента: аргумент типа HeapTupleHeader, переданный в функцию, имя необходимого атрибута и возвращаемый параметр, который сообщает, имеет ли атрибут значение NULL. GetAttributeByName возвращает значение типа Datum, которое можно преобразовать в правильный тип данных с помощью соответствующей функции DatumGetxxx(). Обратите внимание, что возвращаемое значение недействительно, если установлен флаг NULL. Всегда проверяйте этот флаг, прежде чем пытаться что-либо сделать с результатом.

Возврат множеств

Функции языка C имеют два варианта возврата наборов (несколько строк):

  • Режим ValuePerCall — функция, возвращающая множество, вызывается неоднократно (каждый раз с одними и теми же аргументами). Она возвращает одну новую строку при каждом вызове до тех пор, пока не останется строк для возврата, и сигнализирует об этом, возвращая NULL. Поэтому функция возврата набора (Set-Returning Function, SRF) должна сохранять между вызовами свое состояние и возвращать следующее корректное значение при очередном вызове. В примере из раздела Пример создания функции используется режим ValuePerCall.

  • Режим Materialize — SRF заполняет и возвращает объект tuplestore, содержащий результат полностью. Для всего результата происходит только один вызов, и сохранять состояние между вызовами не требуется.

Этот раздел посвящен режиму ValuePerCall. При использовании режима ValuePerCall важно помнить, что выполнение запроса до полного завершения не гарантируется — из-за таких опций, как LIMIT, система может прекратить вызовы функции, возвращающей набор, до того, как все строки будут выбраны. Это означает, что выполнять действия по очистке памяти при последнем вызове небезопасно, поскольку этого может никогда не произойти.

Макросы, поддерживающие ValuePerCall, используют структуру FuncCallContext. FuncCallContext содержит состояние, которое должно сохраняться между вызовами.

typedef struct FuncCallContext
{
    uint64 call_cntr;
    uint64 max_calls;
    void *user_fctx;
    AttInMetadata *attinmeta;
    MemoryContext multi_call_memory_ctx;
    TupleDesc tuple_desc;

} FuncCallContext;
Поля структуры FuncCallContext
Имя Описание

call_cntr

Счетчик числа ранее выполненных вызовов. call_cntr инициализируется 0 при вызове SRF_FIRSTCALL_INIT() и увеличивается каждый раз при вызове SRF_RETURN_NEXT()

max_calls

Максимальное число вызовов, необязательное поле. Если это значение не задано, необходимо предоставить другую возможность определить, когда функция должна завершить свою работу

*user_fctx

Указатель на различную контекстную информацию, предоставленную пользователем, необязательное поле. Можно использовать user_fctx в качестве указателя на ваши данные, чтобы сохранять произвольную контекстную информацию между вызовами функции

*attinmeta

Указатель на структуру, содержащую метаданные типа атрибута, необязательное поле. attinmeta задействуется, когда возвращаются кортежи (т. е. составные типы данных) и не применяется для возврата базовых типов. Он нужен, только если используется BuildTupleFromCStrings() для формирования возвращаемого кортежа

multi_call_memory_ctx

Используется для структур, которые должны сохраняться при нескольких вызовах. multi_call_memory_ctx устанавливается вызовом SRF_FIRSTCALL_INIT() и используется SRF_RETURN_DONE() для очистки. Это наиболее подходящий контекст для любых блоков памяти, которые должны переиспользоваться при повторных вызовах SRF

tuple_desc

Указатель на структуру, содержащую описание кортежа, необязательное поле. tuple_desc задействуется, только когда возвращаются кортежи, и нужен, если вы планируете формировать кортежи с помощью функции heap_form_tuple(), а не BuildTupleFromCStrings(). Сохраняемый в tuple_desc указатель TupleDesc должен сначала пройти через вызов BlessTupleDesc()

Указатель на FuncCallContext внутри вызываемой SRF сохраняется между вызовами в поле fcinfo->flinfo->fn_extra. Макросы автоматически заполняют это поле при первом использовании, рассчитывая прочесть из него тот же указатель при последующих вызовах.

Макросы, используемые с FuncCallContext
Название Описание

SRF_IS_FIRSTCALL()

Используется для определения, вызывается ли функция в первый или в последующий раз

SRF_FIRSTCALL_INIT()

Выполняется при первом вызове только для инициализации FuncCallContext

SRF_PERCALL_SETUP()

Вызывается при каждом вызове функции, включая первый, для подготовки к использованию FuncCallContext

SRF_RETURN_NEXT(funcctx, result)

Используется для возврата данных, если у функции есть данные, которые она должна выдать в текущем вызове. Результат должен иметь тип Datum

SRF_RETURN_DONE(funcctx)

Используется для очистки и завершения SRF, когда функция завершает возврат данных

Полный пример простой SRF, возвращающей составной тип, выглядит так:

/* Файл my_foo.c, скомпилированный в my_foo.so */
#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(my_func);

Datum
my_func(PG_FUNCTION_ARGS)
{
    FuncCallContext     *funcctx;
    int                  call_cntr;
    int                  max_calls;
    TupleDesc            tupdesc;
    AttInMetadata       *attinmeta;

    /* операции при первом вызове функции */
    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext   oldcontext;

        /* создайте контекст функции для персистентности перекрестных вызовов */
        funcctx = SRF_FIRSTCALL_INIT();

        /* переключитесь на контекст памяти, подходящий для нескольких вызовов функции */
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

        /* общее количество кортежей, которые будут возвращены */
        funcctx->max_calls = PG_GETARG_INT32(0);

        /* создайте дескриптор кортежа для типа результата */
        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")));

        /* cгенерируйте метаданные атрибутов, необходимые позже для создания кортежей из необработанных строк C */

        attinmeta = TupleDescGetAttInMetadata(tupdesc);
        funcctx->attinmeta = attinmeta;

        MemoryContextSwitchTo(oldcontext);
    }

    /* операции при каждом вызове функции */
    funcctx = SRF_PERCALL_SETUP();

    call_cntr = funcctx->call_cntr;
    max_calls = funcctx->max_calls;
    attinmeta = funcctx->attinmeta;

    if (call_cntr < max_calls)
    {
        char       **values;
        HeapTuple    tuple;
        Datum        result;

        /*
         * Подготовьте массив значений для построения возвращаемого кортежа.
         * Это должен быть массив строк C,
         * который позже будет обработан функциями ввода типа.
         */

        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", PG_GETARG_INT32(1) * PG_GETARG_INT32(1));
        snprintf(values[2], 16, "%d", PG_GETARG_INT32(1) * PG_GETARG_INT32(1) * PG_GETARG_INT32(1));

        /* постройте кортеж */
        tuple = BuildTupleFromCStrings(attinmeta, values);

        /* создайте Datum */
        result = HeapTupleGetDatum(tuple);

        /* очистка (в данном случае это необязательно) */
        pfree(values[0]);
        pfree(values[1]);
        pfree(values[2]);
        pfree(values);

        SRF_RETURN_NEXT(funcctx, result);
    }
    else
    {
        SRF_RETURN_DONE(funcctx);
    }
}

Объявите эту функцию в PostgreSQL:

CREATE TYPE __my_func AS (f1 integer, f2 integer, f3 integer);

CREATE OR REPLACE FUNCTION my_func(integer, integer)
RETURNS SETOF __my_func
AS '/usr/lib/adpg16/include/server/my_foo.so', 'my_func'
LANGUAGE C IMMUTABLE STRICT;

Другой способ объявить эту функцию — использовать OUT-параметры:

CREATE OR REPLACE FUNCTION my_func(IN integer, IN integer,
    OUT f1 integer, OUT f2 integer, OUT f3 integer)
    RETURNS SETOF record
    AS '/usr/lib/adpg16/include/server/my_foo.so', 'my_func'
    LANGUAGE C IMMUTABLE STRICT;

Вызовите функции:

SELECT my_func(6,5);

Результат:

  my_func
------------
 (5,25,125)
 (5,25,125)
 (5,25,125)
 (5,25,125)
 (5,25,125)
 (5,25,125)
(6 rows)
Нашли ошибку? Выделите текст и нажмите Ctrl+Enter чтобы сообщить о ней