Внутренности PostgreSQL: как добавить новую функцию

4 декабря 2023

При разработке пача для PostgreSQL иногда требуется добавить новую функцию, чтобы ее можно было вызывать из SQL. Недавно вопрос о том, как это делается, задали мне два разных человека в течение одной недели. И хотя это простая задача, информация, по всей видимости, является востребованной. Давайте же рассмотрим решение.

Рассматривать будем на примере конкретного пача 260a1f18, добавляющего в ядро функции to_bin() и to_oct():

=# SELECT to_bin(123);
 to_bin
---------
 1111011

=# SELECT to_bin(255);
  to_bin
----------
 11111111

=# SELECT pg_typeof(to_bin(255));
 pg_typeof
-----------
 text

Добавление функции осуществляется путем редактирования файла pg_proc.dat.

Например:

{ oid => '9030', descr => 'convert int4 number to binary',
  proname => 'to_bin', prorettype => 'text', proargtypes => 'int4',
  prosrc => 'to_bin32' },

Если функция имеет несколько аргументов (proargtypes), их типы указываются через пробел. Можно определить несколько функций с одинаковыми именами (proname), но разными аргументами или их количеством. Само собой разумеется, реализации функции на языке C при этом будут иметь разные имена (prosrc). Перегрузки функций по возвращаемому значению (prorettype) в PostgreSQL не предусмотренно. Описание функции (descr) — это то, что будет показываться в выводе \df+ to_bin.

Для поиска свободного Oid есть специальный скрипт:

$ ./src/include/catalog/unused_oids
...
6312 - 8402
8404 - 9029
9034 - 9999
Patches should use a more-or-less consecutive range of OIDs.
Best practice is to start with a random choice in the range 8000-9999.
Suggested random unused OID: 9223 (777 consecutive OID(s) available ⏎
  starting here)

Еще одно важное свойство функции — это volatility. Оно указывается при помощи параметра provolatile. Значение s говорит, что функция является STABLE. Для одних и тех же аргументов функция возвращает один и тот же результат в рамках одного SQL-выражения. Это поведение функций, к примеру, зависящих от параметров конфигурации (GUCs). Значение v соответствует VOLATILE функциям. Это «грязные» функции в том смысле, что они могут иметь внутреннее состояние, ходить в файлы или сеть, и так далее. Если provolatile не указан, то функция является IMMUTABLE. Это как чистые функции в функциональном программировании. Их возвращаемое значение зависит только от аргументов.

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

Не считая редактирования pg_proc.dat, написание новой функции ничем не отличается от написания новой функции для расширения — см один, два, три и далее по ссылкам. Конкретная реализация to_bin32() может быть найдена в файле varlena.c:

static inline text *
convert_to_base(uint64 value, int base)
{
    const char *digits = "0123456789abcdef";
    char        buf[sizeof(uint64) * BITS_PER_BYTE];
    char       *const end = buf + sizeof(buf);
    char       *ptr = end;

    Assert(base > 1);
    Assert(base <= 16);

    do
    {
        *--ptr = digits[value % base];
        value /= base;
    } while (ptr > buf && value);

    return cstring_to_text_with_len(ptr, end - ptr);
}

Datum
to_bin32(PG_FUNCTION_ARGS)
{
    uint64      value = (uint32) PG_GETARG_INT32(0);

    PG_RETURN_TEXT_P(convert_to_base(value, 2));
}

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

Напоследок хочется отметить два момента. Во-первых, некоторые функции имеет смысл добавлять не в ядро системы, а поместить в отдельное расширение — либо стороннее, либо идущее вместе с PostgreSQL и живущее в каталоге /contrib/. Во-вторых, pg_proc.dat является удобной точкой входа для изучения внутренностей PostgreSQL. Также файл бывает полезен, когда вы примерно понимаете, какую функцию ищите, но не знаете ее название.

Метки: , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.