postgresql-extensions-function-call/

В рамках статьи Учимся писать расширения на языке C для PostgreSQL мы познакомились со структурой расширений для постгреса, узнали, как писать для них тесты, и даже затронули вопрос обновления расширений и использования интерфейса SPI. Но заметка вышла из серии «с места в карьер», без глубокого погружения в детали. А между тем, погружаться есть во что. Хотелось бы заполнить кое-какие пробелы, и начать, пожалуй, следует с Datum и вызова сторонних функций.

Рассмотрим конкретный пример. Допустим, я хочу преобразовать строку в тип Timestamp, или наоборот. Вот как это можно сделать:

Datum
timestamp_in_out_test ( PG_FUNCTION_ARGS )
{
const char * tstamp_str = «Jan 01 00:00:00 2010» ;
Timestamp tstamp ;

tstamp = DatumGetTimestamp ( DirectFunctionCall3 ( timestamp_in ,
CStringGetDatum ( tstamp_str ) ,
ObjectIdGetDatum ( InvalidOid ) ,
Int32GetDatum ( 1 ) ) ) ;

tstamp_str = DatumGetCString ( DirectFunctionCall1 ( timestamp_out ,
TimestampGetDatum ( tstamp ) ) ) ;

PG_RETURN_TEXT_P ( CStringGetTextDatum ( tstamp_str ) ) ;
}

Здесь строка tstamp_str преобразуется в Timestamp. Затем Timestamp преобразуется обратно в char * . Эта строка возвращается в виде результата с SQL-типом TEXT .

Тип Datum и макросы DirectFunctionCall* к этому моменту нам уже знакомы , поэтому не будем на них задерживаться. Куда интереснее другое. Как я узнал, что для сериализации Timestamp в строку есть функция timestamp_out, а для десериализации — timestamp_in? И откуда я знаю, сколько у них аргументов?

В постгресе для любого типа есть текстовое представление. Это нужно для того, чтобы пользователь мог вводить значения разных типов в каком-нибудь psql , а также для отображения значений, прочитанных из таблиц. Функции, преобразующие строку к заданному типу и обратно называются <type>_in и <type>_out . Также есть функции для преобразования одного типа в другой. Они называются <type1>_<type2> . Например, для преобразования Date в Timestamp есть функция date_timestamp .

Узнать, что принимает функция и что она возвращает, можно в psql :

=# df+ timestamp_out
List of functions
-[ RECORD 1 ]——-+—————————-
Schema              | pg_catalog
Name                | timestamp_out
Result data type    | cstring
Argument data types | timestamp without time zone
Type                | func
Volatility          | stable
Parallel            | safe
Owner               | eax
Security            | invoker
Access privileges   |
Language            | internal
Source code         | timestamp_out
Description         | I/O

Если вы ищите функцию, но не знаете точно, как она называется, можно открыть файл src/include/catalog/pg_proc.dat в исходном коде PostgreSQL.

Важно! Обращайте внимание на volatility функций ! Здесь функция является STABLE, то есть, ее поведение зависит от параметров сессии. Это может быть или не быть проблемой, смотря что за задачу вы решаете. Если ваша функция использует STABLE-функции, она тоже должна быть объявлена, как STABLE.

Функция timestamp_in принимает три аргумента. Хотя, казалось бы, мы просто хотим преобразовать строку в Timestamp. Зачем нужно два «лишних» аргумента? Оказывается, что второй аргумент (typelem) на самом деле игнорируется. Третий аргумент (typmod) позволяет производить не очень понятные мне манипуляции. Если вам интересно в этом разобраться, можете почитать полную реализацию в src/backend/utils/adt/timestamp.c . В приведенном примере указывается значение typemod = -1, что означает не производить никаких манипуляций.

Разные функции <type>_in принимают разное количество аргументов, так что будьте внимательны. Например, date_in принимает только cstring. Другой важный момент заключается в том, что cstring — это не то же самое, что text! Первый представляет собой буквально char * , тогда как второй — это объект, представляющий SQL-тип TEXT . Вы не можете передавать text в функцию, ожидающую cstring, или возвращать cstring там, где следует возвращать text. Также ни в коем случае нельзя путать макрос DatumGetTextP с DatumGetCString, и так далее. Если функция вернула cstring, а нам нужен text, можно воспользоваться макросом CStringGetTextDatum. Этот макрос и используется в приведенном примере. Для преобразования в обратную сторону есть TextDatumGetCString.

Важно! В зависимости от платформы, на которой собран постгрес, Datum может хранить 64-х битные числа либо по значению, либо по ссылке. Это означает, что сравнивать два Datum’а напрямую нельзя , даже если вы знаете, что в нем лежит Timestamp! Такой код будет прекрасно работать на x64, но развалится на 32-х битных системах.

Для закрепления материала рассмотрим еще один пример:

Datum
timestamptz_zone_test ( PG_FUNCTION_ARGS )
{
Timestamp result ;
TimestampTz timestamptz = TimestampTzGetDatum ( PG_GETARG_DATUM ( 0 ) ) ;
const char * tzname = «Europe/Moscow» ;

// Timestamp and TimestampTz internally are the same.
// The TZ-version of TIMESTAMP_NOT_FINITE() is not even defined!
if ( TIMESTAMP_NOT_FINITE ( timestamptz ) ) {
// for + or -infinity return + or -infinity
result = ( Timestamp ) timestamptz ;
PG_RETURN_TIMESTAMP ( result ) ;
}

if ( PG_NARGS ( ) > 1 ) {
tzname = TextDatumGetCString ( PG_GETARG_DATUM ( 1 ) ) ;
}

// The code is equal to ‘timestamptz AT TIME ZONE tzname’
result = DatumGetTimestamp ( DirectFunctionCall2 ( timestamptz_zone ,
CStringGetTextDatum ( tzname ) ,
TimestampTzGetDatum ( timestamptz ) ) ) ;

PG_RETURN_TIMESTAMP ( result ) ;
}

Здесь мы имеем дело с перегруженной функцией:

timestamptz_zone_test ( tstamp TIMESTAMPTZ ) TIMESTAMP
timestamptz_zone_test ( tstamp TIMESTAMPTZ , timezone TEXT ) TIMESTAMP

Тонкости работы с часовыми поясами в PostgreSQL ранее были рассмотрены в отдельной статье . Главное, что нужно знать — тип TimestampTz представляет собой время в UTC, точно так же, как и Timestamp. Разница только в том, как эти типы показываются пользователю. TimestampTz отображается в часовом поясе сессии, который можно поменять командой SET TIME ZONE .

Приведенная функция приводит переданное время во время в заданном часовом поясе. Время передается первым аргументом, а часовой пояс вторым. Если часовой пояс не указан, используется значение по умолчанию Europe/Moscow .

Пример интересен тем, что фактически он делает timestamptz AT TIME ZONE tzname . Но как я узнал, что это эквивалентно timestamptz_zone(tzname, timestamptz) ? Если поискать строку AT TIME ZONE по исходному коду PostgreSQL, можно быстро наткнуться на src/backend/parser/gram.y :


| a_expr AT TIME ZONE a_expr            %prec AT
{
$$ = (Node *) makeFuncCall(SystemFuncName(«timezone»),
list_make2($5, $1),
COERCE_SQL_SYNTAX,
@2);
}

Не нужно быть экспертом во Flex и Bison, чтобы понять — здесь вызывается функция timezone . Это перегруженная функция и в зависимости от аргументов будет вызвана та или иная функция на языке C. Смотрим на df+ и видим:


-[ RECORD 5 ]——-+—————————————
Schema              | pg_catalog
Name                | timezone
Result data type    | timestamp without time zone
Argument data types | text, timestamp with time zone
Type                | func
Volatility          | immutable
Parallel            | safe
Owner               | eax
Security            | invoker
Access privileges   |
Language            | internal
Source code         | timestamptz_zone
Description         | adjust timestamp to new time zone

В аргументах text и timestamptz, возвращаемое значение — timestamp. То, что нужно. Этой версии соответствует сишная функция timestamptz_zone . Вот и ответ.

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

Дополнение: В продолжение темы см Расширения PostgreSQL: логирование и исключения , Расширения PostgreSQL: управление памятью , Внутренности PostgreSQL: как добавить новую функцию и далее по ссылкам.

EnglishRussianUkrainian