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

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

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

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

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

Если для процедурного языка предоставляется валидатор, он должен быть объявлен как функция, принимающая один параметр типа oid. Результат валидатора игнорируется, поэтому обычно он объявляется как возвращающий тип void. Валидатор будет вызываться в конце выполнения команды CREATE FUNCTION, создающей или изменяющей функцию на процедурном языке. Переданный ему 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. Информацию о написании расширений см. в разделе Упаковка связанных объектов в расширение.

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