Перейти к основному содержимому

Написание обработчика процедурного языка

Все вызовы функций, написанных на языке, отличном от текущего интерфейса «версии 1» для компилируемых языков (сюда входят функции на процедурных языках, определяемых пользователем, и функции, написанные на SQL), проходят через функцию-обработчик вызова для конкретного языка. В обязанности обработчика вызова входит выполнение функции осмысленным способом, например, путем интерпретации исходного текста. В этой главе описывается, как можно написать обработчик вызова нового процедурного языка.

Обработчик вызова процедурного языка - это «обычная» функция, которая должна быть написана на компилируемом языке, таком как C, с использованием интерфейса версии 1 и зарегистрирована в PostgreSQL как не принимающая аргументов и возвращающая тип language_handler. Этот специальный псевдотип идентифицирует функцию как обработчик вызовов и не позволяет вызывать ее непосредственно в командах SQL.

Обработчик вызова вызывается так же, как и любая другая функция: Он получает указатель на структуру FunctionCallInfoBaseData, содержащую значения аргументов и информацию о вызываемой функции, и должен вернуть результат Datum (и, возможно, установить поле isnull структуры FunctionCallInfoBaseData, если он хочет вернуть нулевой результат SQL). Разница между обработчиком вызова и обычной вызываемой функцией заключается в том, что поле flinfo->fn_oid структуры FunctionCallInfoBaseData будет содержать OID фактической вызываемой функции, а не самого обработчика вызова. Обработчик вызова должен использовать это поле для определения функции, которую следует выполнить. Кроме того, передаваемый список аргументов был настроен в соответствии с объявлением целевой функции, а не обработчика вызова.

Обработчик вызова должен получить запись функции из системного каталога pg_proc и проанализировать типы аргументов и возврата вызываемой функции. Клаузула AS из команды CREATE FUNCTION для функции будет найдена в столбце prosrc строки pg_proc. Обычно это исходный текст процедурного языка, но теоретически это может быть и что-то другое, например, имя пути к файлу или что-либо еще, что подробно указывает обработчику вызова, что нужно делать.

Часто одна и та же функция вызывается много раз в одном SQL-операторе. Обработчик вызова может избежать повторного поиска информации о вызываемой функции, используя поле flinfo-\>fn_extra. Изначально оно будет NULL, но может быть установлено обработчиком вызова для указания на информацию о вызванной функции. При последующих вызовах, если flinfo->fn_extra уже не является NULL, его можно использовать и пропустить шаг поиска в формации. Обработчик вызова должен убедиться, что flinfo->fn_extra указывает на память, которая будет жить по крайней мере до конца текущего запроса, поскольку структура данных FmgrInfo может храниться так долго. Один из способов сделать это - выделить дополнительные данные в контексте памяти, указанном flinfo->fn_mcxt; такие данные обычно имеют такое же время жизни, как и сама FmgrInfo. Но обработчик может также выбрать более долгоживущий контекст памяти, чтобы кэшировать информацию об определении функции при разных запросах.

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

В src/test/modules/plsample находится шаблон обработчика на процедурном языке, написанный как расширение на C. Это рабочий пример, демонстрирующий один из способов создания обработчика на процедурном языке, обработки параметров и возврата значения.

Хотя для создания минимального процедурного языка достаточно предоставить обработчик вызовов, есть еще две функции, которые могут быть предоставлены дополнительно, чтобы сделать язык более удобным в использовании. Это валидатор и встроенный обработчик. Валидатор может быть предоставлен для проверки специфики языка при создании функции CREATE FUNCTION. Встроенный обработчик может быть предоставлен для того, чтобы язык мог поддерживать анонимные блоки кода, выполняемые с помощью команды DO.

Если валидатор предоставляется процедурным языком, он должен быть объявлен как функция, принимающая один параметр типа oid. Результат работы валидатора игнорируется, поэтому он обычно объявляется с возвратом void. Валидатор будет вызван в конце команды CREATE FUNCTION, которая создала или обновила функцию, написанную на процедурном языке. Передаваемый OID - это OID строки pg_proc функции. Валидатор должен получить эту строку обычным способом и выполнить все необходимые проверки. Во-первых, вызовите CheckFunctionValidatorAccess(), чтобы диагностировать явные вызовы валидатора, которые пользователь не смог выполнить через CREATE FUNCTION. Типичные проверки включают проверку того, что типы аргументов и результатов функции поддерживаются языком, и что тело функции синтаксически корректно в языке. Если валидатор считает, что функция в порядке, он должен просто вернуться. Если он обнаружит ошибку, то должен сообщить об этом через обычный механизм сообщений об ошибках ereport(). Выброс ошибки приведет к откату транзакции и тем самым предотвратит фиксацию неправильного определения функции.

Функции валидатора, как правило, должны учитывать параметр check_function_bodies: если он выключен, то любые дорогостоящие или контекстно-зависимые проверки должны быть пропущены. Если язык предусматривает выполнение кода во время компиляции, валидатор должен подавлять проверки, которые могут вызвать такое выполнение. В частности, этот параметр отключен в pg_dump, чтобы он мог загружать функции процедурного языка, не заботясь о побочных эффектах или зависимостях тел функций от других объектов базы данных. (Из-за этого требования обработчик вызова не должен предполагать, что валидатор полностью проверил функцию. Смысл наличия валидатора заключается не в том, чтобы позволить обработчику вызовов опускать проверки, а в том, чтобы немедленно уведомить пользователя о наличии очевидных ошибок в команде CREATE FUNCTION.) Хотя выбор того, что именно проверять, в основном остается на усмотрение функции-валидатора, обратите внимание, что основной код CREATE FUNCTION выполняет клаузулы SET, присоединенные к функции, только если включена функция check_function_bodies. Поэтому проверки, на результаты которых могут повлиять параметры GUC, определенно должны быть пропущены при выключенной check_function_bodies, чтобы избежать ложных отказов при восстановлении дампа.

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

Рекомендуется обернуть все эти объявления функций, а также саму команду CREATE LANGUAGE в расширение, чтобы для установки языка было достаточно простой команды CREATE EXTENSION.

Процедурные языки, включенные в стандартный дистрибутив, являются хорошим ориентиром при попытке написать свой собственный обработчик языка. Загляните в подкаталог src/pl дерева исходных текстов.