Chapter 57. Escritura de un manejador de lenguaje procedimental

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.