При разработке пача для PostgreSQL иногда требуется добавить новую функцию, чтобы ее можно было вызывать из SQL. Недавно вопрос о том, как это делается, задали мне два разных человека в течение одной недели. И хотя это простая задача, информация, по всей видимости, является востребованной. Давайте же рассмотрим решение.
Рассматривать будем на примере конкретного пача 260a1f18 , добавляющего в ядро функции to_bin()
и to_oct()
:
to_bin
———
1111011
=# SELECT to_bin(255);
to_bin
———-
11111111
=# SELECT pg_typeof(to_bin(255));
pg_typeof
————
text
Добавление функции осуществляется путем редактирования файла pg_proc.dat.
Например:
proname => ‘to_bin’, prorettype => ‘text’, proargtypes => ‘int4’,
prosrc => ‘to_bin32’ },
Если функция имеет несколько аргументов ( proargtypes
), их типы указываются через пробел. Можно определить несколько функций с одинаковыми именами ( proname
), но разными аргументами или их количеством. Само собой разумеется, реализации функции на языке C при этом будут иметь разные имена ( prosrc
). Перегрузки функций по возвращаемому значению ( prorettype
) в PostgreSQL не предусмотренно. Описание функции ( descr
) — это то, что будет показываться в выводе df+ to_bin
.
Для поиска свободного Oid есть специальный скрипт:
…
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:
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. Также файл бывает полезен, когда вы примерно понимаете, какую функцию ищите, но не знаете ее название.