PL/Tcl es un lenguaje procedural para el gestor de bases de datos Postgres que permite el uso de Tcl para la creación de funciones y procedimientos desencadenados por eventos.
Este paquete fue escrito originalmente por Jan Wieck.
PL/Tcl ofrece la mayoría de las capacidades de que dispone el lenguaje C, excepto algunas restricciones.
Las restricciones buenas son que todo se ejecuta en un buen interprete Tcl. Además del reducido juego de ordenes de Tcl, solo se disponen de unas pocas ordenes para acceder a bases de datos a través de SPI y para enviar mensajes mediante elog(). No hay forma de acceder a las interioridades del proceso de gestión de la base de datos, no de obtener acceso al nivel del sistema operativo, bajo los permisos del identificador de usuario de Postgres, como es posible en C. Así, cualquier usuario de bases de datos sin privilegios puede usar este lenguaje.
La otra restricción, interna, es que los procedimientos Tcl no pueden usarse para crear funciones de entrada / salida para nuevos tipos de datos.
Los objetos compartidos para el gestor de llamada PL/Tcl se construyen automáticamente y se instalan en el directorio de bibliotecas de Postgres, si el soporte de Tcl/Tk ha sido especificado durante la configuración, en el procedimiento de instalación.
En Postgres, un mismo nombre de función puede usarse para diferentes funciones, siempre que el numero de argumentos o sus tipos sean distintos. Esto puede ocasionar conflictos con los nombres de procedimientos Tcl. Para ofrecer la misma flexibilidad en PL/Tcl, los nombres de procedimientos Tcl internos contienen el identificador de objeto de la fila de procedimientos pg_proc como parte de sus nombres. Así, diferentes versiones (por el numero de argumentos) de una misma función de Postgres pueden ser diferentes también para Tcl.
Para crear una función en el lenguaje PL/Tcl, se usa la sintaxis
CREATE FUNCTION funcname argumen) RETURNS returntype AS ' # PL/Tcl function body ' LANGUAGE 'pltcl'; |
CREATE FUNCTION tcl_max (int4, int4) RETURNS int4 AS ' if {$1 > $2} {return $1} return $2 ' LANGUAGE 'pltcl'; |
CREATE FUNCTION overpaid_2 (EMP) RETURNS bool AS ' if {200000.0 < $1(salary)} { return "t" } if {$1(age) < 30 && 100000.0 < $1(salary)} { return "t" } return "f" ' LANGUAGE 'pltcl'; |
A veces (especialmente cuando se usan las funciones SPI que se describirán más adelante) es útil tener algunos datos globales que se mantengan entre dos llamadas al procedimiento. Todos los procedimientos PL/Tcl ejecutados por un backend comparten el mismo interprete de Tcl. Para ayudar a proteger a los procedimientos PL/Tcl de efectos secundarios, una matriz queda disponible para cada uno de los procedimientos a través de la orden 'upvar'. El nombre global de esa variable es el nombre interno asignado por el procedimiento, y el nombre local es GD.
Los procedimientos desencadenados se definen en Postgres como funciones sin argumento y que devuelven un tipo opaco. Y lo mismo en el lenguaje PL/Tcl.
La información del gestor de procedimientos desencadenados se pasan al cuerpo del procedimiento en las siguientes variables:
El nombre del procedimiento disparador se toma de la sentencia CREATE TRIGGER.
El ID de objeto de la tabla que provoca el desencadenamiento ha de ser invocado.
Una lista Tcl de los nombres de campos de las tablas, precedida de un elemento de lista vacío. Esto se hace para que al buscar un nombre de elemento en la lista con la orden de Tcl 'lsearch', se devuelva el mismo numero positivo, comenzando por 1, en el que los campos están numerados en el catalogo de sistema 'pg_attribute'.
La cadena BEFORE o AFTER, dependiendo del suceso de la llamada desencadenante.
La cadena ROW o STATEMENT, dependiendo del suceso de la llamada desencadenante.
La cadena INSERT, UPDATE o DELETE, dependiendo del suceso de la llamada desencadenante.
Una matriz que contiene los valores de la fila de la nueva tabla para acciones INSERT/UPDATE, o vacía para DELETE.
Una matriz que contiene los valores de la fila de la vieja tabla para acciones UPDATE o DELETE, o vacía para INSERT.
La matriz de datos de estado global, como se describa más adelante.
Una lista Tcl de los argumentos del procedimiento como se dan en la sentencia CREATE TRIGGER. Los argumentos son también accesibles como $1 ... $n en el cuerpo del procedimiento.
EL valor devuelto por un procedimiento desencadenado es una de las cadenas OK o SKIP, o una lista devuelta por la orden Tcl 'array get'. Si el valor devuelto es OK, la operación normal que ha desencadenado el procedimiento (INSERT/UPDATE/DELETE) tendrá lugar. Obviamente, SKIP le dice al gestor de procesos desencadenados que suprima silenciosamente la operación. La lista de 'array get' le dice a PL/Tcl que devuelva una fila modificada al gestor de procedimientos desencadenados que será insertada en lugar de la dada en $NEW (solo para INSERT/UPDATE). No hay que decir que todo esto solo tiene sentido cuando el desencadenante es BEFORE y FOR EACH ROW.
Ha aquí un pequeño ejemplo de procedimiento desencadenado que fuerza a un valor entero de una tabla a seguir la pista del numero de actualizaciones que se han realizado en esa fila. Para cada nueva fila insertada, el valor es inicializado a 0, e incrementada en cada operación de actualización:
CREATE FUNCTION trigfunc_modcount() RETURNS OPAQUE AS ' switch $TG_op { INSERT { set NEW($1) 0 } UPDATE { set NEW($1) $OLD($1) incr NEW($1) } default { return OK } } return [array get NEW] ' LANGUAGE 'pltcl'; CREATE TABLE mytab (num int4, modcnt int4, desc text); CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab FOR EACH ROW EXECUTE PROCEDURE trigfunc_modcount('modcnt'); |
Las siguientes ordenes permiten acceder a una base de datos desde el interior de un procedimiento PL/Tcl:
Lanza un mensaje de registro. Los posibles niveles son NOTICE, WARN, ERROR, FATAL, DEBUG y NOIND, como en la función 'elog()' de C.
Duplica todas las apariciones de una comilla o de la barra invertida. Debería usarse cuando las variables se usen en la cadena en la cadena de la consulta enviada a 'spi_exec' o 'spi_prepara' (no en la lista de valores de 'spi_execp'). Consideremos una cadena de consulta como esta:
"SELECT '$val' AS ret" |
"SELECT 'doesn't' AS ret" |
"SELECT 'doesn''t' AS ret" |
"SELECT '[ quote $val ]' AS ret" |
Llama al analizador/planificador/optimizador/ejecutos de la consulta. El valor opcional -count la dice a 'spi_exec' el máximo numero de filas que han de ser procesadas por la consulta.
Si la consulta es una sentencia SELECT y se incluye el cuerpo del lazo opcional (un cuerpo de sentencias Tcl similar a una sentencia anticipada), se evalúa para cada fila seleccionada, y se comporta como se espera, tras continua/break. Los valores de los campos seleccionados se colocan en nombres de variables, como nombres de columnas. Así,
spi_exec "SELECT count(*) AS cnt FROM pg_proc" |
spi_exec -array C "SELECT * FROM pg_class" { elog DEBUG "have table $C(relname)" } |
Prepara Y GUARDA una consulta para una ejecución posterior. Es un poco distinto del caso de C, ya que en ese caso, la consulta prevista es automáticamente copiada en el contexto de memoria de mayor nivel. Por lo tanto, no actualmente ninguna forma de planificar una consulta sin guardarla.
Si la consulta hace referencia a argumentos, los nombres de los tipos han de incluirse, en forma de lista Tcl. El valor devuelto por 'spi_prepare' es el identificador de la consulta que se usará en las siguientes llamadas a 'spi_execp'. Véase 'spi_execp' para un ejemplo.
Ejecuta una consulta preparada en 'spi_prepare' con sustitución de variables. El valor opcional '-count' le dice a 'spi_execp' el máximo numero de filas que se procesarán en la consulta.
El valor opcional para '-nulls' es una cadena de espacios de longitud "n", que le indica a 'spi_execp' qué valores son NULL. Si se indica, debe tener exactamente la longitud del numero de valores.
El identificador de la consulta es el identificador devuelto por la llamada a 'spi_prepare'.
Si se pasa una lista de tipos a 'spi_prepare', ha de pasarse una lista Tcl de exactamente la misma longitud a 'spi_execp' después de la consulta. Si la lista de tipos de 'spi_prepare' está vacía, este argumento puede omitirse.
Si la consulta es una sentencia SELECT, lo que se ha descrito para 'spi_exec' ocurrirá para el cuerpo del bucle y las variables de los campos seleccionados.
He aquí un ejemplo de una función PL/Tcl que usa una consulta planificada:
CREATE FUNCTION t1_count(int4, int4) RETURNS int4 AS ' if {![ info exists GD(plan) ]} { # prepare the saved plan on the first call set GD(plan) [ spi_prepare \\ "SELECT count(*) AS cnt FROM t1 WHERE num >= \\$1 AND num <= \\$2" \\ int4 ] } spi_execp -count 1 $GD(plan) [ list $1 $2 ] return $cnt ' LANGUAGE 'pltcl'; |
PL/Tcl tiene una característica especial para cosas que suceden raramente. Reconoce dos tablas "mágicas", 'pltcl_modules' y 'pltcl_modfuncs'. Si existen, el módulo 'desconocido' es cargado por el interprete, inmediatamente tras su creación. Cada vez que se invoca un procedimiento Tcl desconocido, el procedimiento 'desconocido' es comprobado, por si el procedimiento en cuestión está definido en uno de esos módulos. Si ocurre esto, el módulo es cargado cuando sea necesario. Para habilitar este comportamiento, el gestor de llamadas de PL/Tcl ha de ser compilado con la opción -DPLTCL_UNKNOWN_SUPPORT habilitado.
Existen scripts de soporte para mantener esas tablas en el subdirectorio de módulos del código fuente de PL/Tcl, incluyendo el código fuente del módulo 'desconocido', que ha de ser instalado inicialmente.