Todas las llamadas a funciones que están escritas en un lenguaje diferente a la interfaz actual “versión 1” para lenguajes compilados (esto incluye funciones en lenguajes procedimentales definidos por el usuario y funciones escritas en SQL) pasan a través de una función manejadora de llamadas (call handler) para el lenguaje específico. Es responsabilidad del manejador de llamadas ejecutar la función de una manera significativa, como por ejemplo interpretando el texto fuente suministrado. Este capítulo describe cómo se puede escribir el manejador de llamadas de un nuevo lenguaje procedimental.
El manejador de llamadas para un lenguaje procedimental es una
función “normal” que debe escribirse en un lenguaje compilado
como C, utilizando la interfaz versión-1, y registrada en
PostgreSQL como una función que no recibe argumentos
y devuelve el tipo language_handler. Este
pseudotipo especial identifica la función como un manejador de llamadas y
evita que sea llamada directamente en comandos SQL.
Para más detalles sobre las convenciones de llamada en lenguaje C y la carga dinámica,
consulta la Section 36.10.
El manejador de llamadas se invoca de la misma manera que cualquier otra función:
Recibe un puntero a una estructura struct FunctionCallInfoBaseData que contiene
los valores de los argumentos e información sobre la función llamada, y se
espera que devuelva un resultado de tipo Datum (y posiblemente
establezca el campo isnull de la estructura
FunctionCallInfoBaseData, si desea
devolver un resultado nulo de SQL). La diferencia entre un manejador de
llamadas y una función llamada ordinaria es que el campo
flinfo->fn_oid de la estructura
FunctionCallInfoBaseData contendrá
el OID de la función real a llamar, no del propio manejador de
llamadas. El manejador de llamadas debe usar este campo para determinar
qué función ejecutar. Además, la lista de argumentos pasados
se ha configurado de acuerdo con la declaración de la función de destino,
no del manejador de llamadas.
Depende del manejador de llamadas recuperar la entrada de la función del
catálogo del sistema pg_proc y analizar los tipos de argumentos
y de retorno de la función llamada. La cláusula AS del
comando CREATE FUNCTION para la función se encontrará
en la columna prosrc de la fila de
pg_proc. Esto suele ser el texto fuente en el lenguaje
procedimental, pero en teoría podría ser otra cosa, como la ruta a un archivo,
o cualquier otra cosa que le indique detalladamente al manejador de llamadas qué hacer.
A menudo, la misma función es llamada muchas veces por sentencia SQL.
Un manejador de llamadas puede evitar búsquedas repetidas de información sobre la
función llamada utilizando el campo
flinfo->fn_extra. Este será
inicialmente NULL, pero el manejador de llamadas puede establecerlo para que apunte a
la información sobre la función llamada. En llamadas posteriores, si
flinfo->fn_extra ya no es NULL,
entonces se puede utilizar y se omite el paso de búsqueda de información. El
manejador de llamadas debe asegurarse de que
flinfo->fn_extra apunte a
memoria que viva al menos hasta el final de la consulta actual,
ya que una estructura de datos FmgrInfo podría
mantenerse durante ese tiempo. Una forma de hacerlo es asignar los datos adicionales
en el contexto de memoria especificado por
flinfo->fn_mcxt; dichos datos
normalmente tendrán la misma vida útil que el propio
FmgrInfo. Pero el manejador también podría
optar por utilizar un contexto de memoria de mayor duración para poder almacenar en caché
la información de definición de la función a través de las consultas.
Cuando se invoca una función de lenguaje procedimental como un disparador (trigger), no se pasan
argumentos de la manera habitual, sino que el campo
context de FunctionCallInfoBaseData
apunta a una estructura TriggerData, en lugar de ser NULL
como lo es en una llamada a función normal. Un manejador de lenguaje debe
proporcionar mecanismos para que las funciones de lenguaje procedimental obtengan la
información del disparador.
Se proporciona una plantilla para un manejador de lenguaje procedimental escrito como una extensión en C
en src/test/modules/plsample. Este es un
ejemplo funcional que demuestra una forma de crear un manejador de lenguaje
procedimental, procesar parámetros y devolver un valor.
Aunque proporcionar un manejador de llamadas es suficiente para crear un lenguaje procedimental mínimo, hay otras dos funciones que opcionalmente se pueden proporcionar para hacer que el lenguaje sea más cómodo de usar. Estas son un validador (validator) y un manejador en línea (inline handler). Se puede proporcionar un validador para permitir que se realicen comprobaciones específicas del lenguaje durante CREATE FUNCTION. Se puede proporcionar un manejador en línea para permitir que el lenguaje admita bloques de código anónimos ejecutados mediante el comando DO.
Si un lenguaje procedimental proporciona un validador, este
debe declararse como una función que recibe un único parámetro de tipo
oid. El resultado del validador se ignora, por lo que habitualmente
se declara que devuelve void. El validador se llamará al
final de un comando CREATE FUNCTION que haya creado
o actualizado una función escrita en el lenguaje procedimental.
El OID pasado es el OID de la fila de la función en pg_proc.
El validador debe recuperar esta fila de la manera habitual y realizar
las comprobaciones que sean apropiadas.
Primero, llama a CheckFunctionValidatorAccess() para diagnosticar
llamadas explícitas al validador que el usuario no podría lograr a través de
CREATE FUNCTION. Las comprobaciones típicas incluyen verificar
que los tipos de argumentos y de resultado de la función sean compatibles con el
lenguaje, y que el cuerpo de la función sea sintácticamente correcto
en el lenguaje. Si el validador considera que la función es correcta,
simplemente debe retornar. Si encuentra un error, debe reportarlo
a través del mecanismo normal de reporte de errores ereport().
Lanzar un error forzará la reversión (rollback) de la transacción y, por lo tanto, evitará
que se confirme la definición incorrecta de la función.
Las funciones validadoras normalmente deben respetar el parámetro check_function_bodies: si está desactivado,
se debe omitir cualquier comprobación costosa o sensible al contexto. Si el
lenguaje permite la ejecución de código en tiempo de compilación, el validador
debe suprimir las comprobaciones que confundirían a dicha ejecución. En particular,
este parámetro es desactivado por pg_dump para que pueda
cargar funciones de lenguaje procedimental sin preocuparse por los efectos secundarios o
las dependencias de los cuerpos de las funciones en otros objetos de la base de datos.
(Debido a este requisito, el manejador de llamadas debe evitar
asumir que el validador ha comprobado completamente la función. El propósito
de tener un validador no es permitir que el manejador de llamadas omita comprobaciones, sino
notificar al usuario de inmediato si hay errores obvios en un
comando CREATE FUNCTION).
Aunque la elección de qué comprobar exactamente se deja principalmente a la
discreción de la función validadora, ten en cuenta que el código principal de
CREATE FUNCTION solo ejecuta las cláusulas SET
asociadas a una función cuando check_function_bodies está activado.
Por lo tanto, las comprobaciones cuyos resultados puedan verse afectados por los parámetros GUC
definitivamente deben omitirse cuando check_function_bodies está
desactivado, para evitar fallos falsos al restaurar un respaldo (dump).
Si un lenguaje procedimental proporciona un manejador en línea, este
debe declararse como una función que recibe un único parámetro de tipo
internal. El resultado del manejador en línea se ignora, por lo que
habitualmente se declara que devuelve void. El manejador en línea
se llamará cuando se ejecute una sentencia DO que especifique
el lenguaje procedimental. El parámetro realmente pasado es un puntero
a una estructura InlineCodeBlock, que contiene información
sobre los parámetros de la sentencia DO, en particular el
texto del bloque de código anónimo a ejecutar. El manejador en línea
debe ejecutar este código y retornar.
Se recomienda que envuelvas todas estas declaraciones de funciones,
así como el propio comando CREATE LANGUAGE, en
una extensión para que un simple comando CREATE EXTENSION
sea suficiente para instalar el lenguaje. Consulta la
Section 36.17 para obtener información sobre cómo escribir
extensiones.
Los lenguajes procedimentales incluidos en la distribución estándar
son buenas referencias a la hora de intentar escribir tu propio manejador de lenguaje.
Busca en el subdirectorio src/pl del árbol de fuentes.
La página de referencia de CREATE LANGUAGE
también tiene algunos detalles útiles.