Глава 55. Написание обработчика процедурного языка#

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

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

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

Обработчик вызова для процедурного языка - это обычная функция, которая должна быть написана на компилируемом языке, таком как C, с использованием интерфейса версии 1, и зарегистрирована в Tantor SE-1C как функция, не принимающая аргументов и возвращающая тип 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 также содержит некоторые полезные детали.