Глава 55. Написание обработчика процедурного языка#
Глава 55. Написание обработчика процедурного языка
Все вызовы функций, написанных на языке, отличном от текущего интерфейса версии 1 для компилируемых языков (это включает функции на языках процедурного программирования, определенных пользователем, и функции, написанные на SQL), проходят через функцию обработчика вызовов для конкретного языка. Обязанность обработчика вызовов заключается в выполнении функции в значимом смысле, например, путем интерпретации предоставленного исходного текста. В этой главе описывается, как можно написать обработчик вызовов для нового процедурного языка.
Обработчик вызова для процедурного языка - это “обычная” функция, которая должна быть написана на компилируемом языке, таком как C, с использованием интерфейса версии 1, и зарегистрирована в Tantor BE как функция, не принимающая аргументов и возвращающая тип language_handler
. Этот специальный псевдотип идентифицирует функцию как обработчик вызова и предотвращает ее прямой вызов в SQL-командах.
Вызов обработчика вызывается так же, как и любая другая функция:
Он получает указатель на структуру FunctionCallInfoBaseData
struct
, содержащую
значения аргументов и информацию о вызываемой функции, и ожидается, что он
вернет результат Datum
(и, возможно,
установит поле isnull
структуры
FunctionCallInfoBaseData
, если он хочет
вернуть SQL-значение null). Разница между обработчиком вызова и обычной вызываемой функцией заключается в том, что
поле 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
. Но обработчик также может выбрать использование контекста памяти с более длительным сроком службы, чтобы кешировать информацию о определении функции между запросами.
Когда процедурная функция языка вызывается как триггер, аргументы не передаются обычным образом, но поле context
FunctionCallInfoBaseData
указывает на структуру TriggerData
, а не является NULL
, как в обычном вызове функции. Обработчик языка должен предоставить механизмы для получения информации о триггере в процедурных функциях.
Шаблон обработчика процедурного языка, написанного в виде расширения на C, предоставляется в src/test/modules/plsample
. Это рабочий пример, демонстрирующий один из способов создания обработчика процедурного языка, обработки параметров и возврата значения.
Хотя предоставление обработчика вызова достаточно для создания минимального процедурного языка, есть еще две другие функции, которые могут быть предоставлены для удобства использования языка. Это валидатор и встроенный обработчик. Валидатор может быть предоставлен для выполнения языкоспецифичной проверки во время 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
для установки языка. См. Раздел 35.16 для получения информации о написании расширений.
Процедурные языки, включенные в стандартную дистрибуцию, являются хорошими источниками информации при попытке написать собственный обработчик языка. Обратитесь к подкаталогу src/pl
дерева исходного кода. Страница справки CREATE LANGUAGE также содержит некоторые полезные детали.