Esta sección describe algunos detalles de implementación que con frecuencia es importante que conozcan los usuarios de PL/pgSQL.
Las instrucciones y expresiones SQL dentro de una función PL/pgSQL pueden hacer referencia a variables y parámetros de la función. Tras bambalinas, PL/pgSQL sustituye los parámetros de consulta por dichas referencias. Los parámetros de consulta solo se sustituirán en lugares donde sean sintácticamente permitidos. Como caso extremo, considera este ejemplo de mal estilo de programación:
INSERT INTO foo (foo) VALUES (foo(foo));
La primera aparición de foo debe ser sintácticamente un nombre de tabla,
por lo que no se sustituirá, incluso si la función tiene una variable llamada foo.
La segunda aparición debe ser el nombre de una columna de esa tabla, por lo que tampoco se
sustituirá. Del mismo modo, la tercera aparición debe ser un nombre de función, por lo que
tampoco se sustituirá. Solo la última aparición es candidata a ser una referencia a una
variable de la función PL/pgSQL.
Otra forma de entender esto es que la sustitución de variables solo puede insertar valores de datos en un comando SQL; no puede cambiar dinámicamente a qué objetos de base de datos hace referencia el comando. (Si deseas hacer eso, debes construir una cadena de comando dinámicamente, como se explica en la Section 41.5.4).
Dado que los nombres de las variables sintácticamente no difieren de los nombres de las columnas de la tabla, puede haber ambigüedad en las instrucciones que también hacen referencia a tablas: ¿se refiere un nombre dado a una columna de tabla o a una variable? Cambiemos el ejemplo anterior a:
INSERT INTO dest (col) SELECT foo + bar FROM src;
Aquí, dest y src deben ser nombres de tabla, y
col debe ser una columna de dest, pero foo
y bar podrían ser razonablemente tanto variables de la función como
columnas de src.
Por defecto, PL/pgSQL reportará un error si un nombre en una instrucción SQL pudiera referirse tanto a una variable como a una columna de tabla. Puedes solucionar este problema cambiando el nombre de la variable o columna, calificando la referencia ambigua o indicándole a PL/pgSQL qué interpretación preferir.
La solución más sencilla es cambiar el nombre de la variable o columna. Una regla de codificación
común es utilizar una convención de nomenclatura diferente para las variables de
PL/pgSQL de la que se utiliza para los nombres de columnas.
Por ejemplo, si nombras consistentemente las variables de función como
v_ mientras que ninguno de los nombres de tus
columnas comienza con algov_, no ocurrirá ningún conflicto.
Alternativamente, puedes calificar las referencias ambiguas para aclararlas. En el ejemplo anterior,
src.foo sería una referencia inequívoca a la columna de la tabla. Para crear una
referencia inequívoca a una variable, declárala en un bloque etiquetado y utiliza la etiqueta del bloque
(consulta la Section 41.2). Por ejemplo:
<<block>>
DECLARE
foo int;
BEGIN
foo := ...;
INSERT INTO dest (col) SELECT block.foo + bar FROM src;
Aquí, block.foo significa la variable incluso si hay una columna foo
en src. Los parámetros de la función, así como las variables especiales como
FOUND, se pueden calificar con el nombre de la función, porque se declaran
implícitamente en un bloque externo etiquetado con el nombre de la función.
A veces no es práctico corregir todas las referencias ambiguas en un gran cuerpo de código PL/pgSQL. En tales casos, puedes especificar que PL/pgSQL resuelva las referencias ambiguas como la variable (lo que es compatible con el comportamiento de PL/pgSQL antes de PostgreSQL 9.0), o como la columna de la tabla (lo que es compatible con algunos otros sistemas como Oracle).
Para cambiar este comportamiento para todo el sistema, establece el parámetro de configuración
plpgsql.variable_conflict a uno de error,
use_variable o use_column (donde error
es el valor predeterminado de fábrica). Este parámetro afecta a las compilaciones posteriores de
instrucciones en las funciones PL/pgSQL, pero no a las instrucciones
ya compiladas en la sesión actual. Debido a que cambiar esta configuración puede causar cambios
inesperados en el comportamiento de las funciones PL/pgSQL, solo
puede ser modificado por un superusuario.
También puedes establecer el comportamiento función por función, insertando uno de estos comandos especiales al inicio del texto de la función:
#variable_conflict error #variable_conflict use_variable #variable_conflict use_column
Estos comandos afectan solo a la función en la que están escritos y anulan la configuración de
plpgsql.variable_conflict. Un ejemplo es:
CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
#variable_conflict use_variable
DECLARE
curtime timestamp := now();
BEGIN
UPDATE users SET last_modified = curtime, comment = comment
WHERE users.id = id;
END;
$$ LANGUAGE plpgsql;
En el comando UPDATE, curtime, comment e
id se referirán a la variable y parámetros de la función, tengan o no las
tablas users columnas con esos nombres. Ten en cuenta que tuvimos que calificar la
referencia a users.id en la cláusula WHERE para que se refiera a
la columna de la tabla. Pero no tuvimos que calificar la referencia a comment
como destino en la lista de UPDATE, porque sintácticamente debe ser una columna
de users. Podríamos escribir la misma función sin depender de la configuración
de variable_conflict de esta manera:
CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
<<fn>>
DECLARE
curtime timestamp := now();
BEGIN
UPDATE users SET last_modified = fn.curtime, comment = stamp_user.comment
WHERE users.id = stamp_user.id;
END;
$$ LANGUAGE plpgsql;
La sustitución de variables no ocurre en una cadena de comando dada a EXECUTE
o a una de sus variantes. Si necesitas insertar un valor variable en dicho comando, hazlo como
parte de la construcción del valor de la cadena, o utiliza USING, como se ilustra
en la Section 41.5.4.
La sustitución de variables actualmente funciona solo en SELECT,
INSERT, UPDATE, DELETE,
MERGE y comandos que contienen uno de estos (como EXPLAIN y
CREATE TABLE ... AS SELECT), porque el motor SQL principal permite parámetros
de consulta solo en estos comandos. Para utilizar un nombre o valor no constante en otros tipos de
instrucciones (genéricamente llamadas instrucciones de utilidad), debes construir la instrucción
de utilidad como una cadena y ejecutarla con EXECUTE.
El intérprete de PL/pgSQL analiza el texto fuente de la función y produce un árbol de instrucciones binario interno la primera vez que se llama a la función (dentro de cada sesión). El árbol de instrucciones traduce completamente la estructura de la instrucción PL/pgSQL, pero las expresiones SQL individuales y los comandos SQL utilizados en la función no se traducen de inmediato.
A medida que cada expresión y comando SQL se ejecuta por primera vez en la función, el intérprete
de PL/pgSQL analiza y examina el comando para crear una instrucción
preparada, utilizando la función SPI_prepare del gestor SPI. Las visitas
posteriores a esa expresión o comando reutilizan la instrucción preparada. Por lo tanto, una función
con rutas de código condicionales que rara vez se visitan nunca incurrirá en la sobrecarga de analizar
aquellos comandos que nunca se ejecutan dentro de la sesión actual. Una desventaja es que los errores
en una expresión o comando específico no se pueden detectar hasta que esa parte de la función se alcanza
en la ejecución. (Los errores de sintaxis triviales se detectarán durante la fase de análisis inicial,
pero cualquier error más profundo no se detectará hasta la ejecución).
PL/pgSQL (o más precisamente, el gestor SPI) puede además intentar almacenar en caché el plan de ejecución asociado con cualquier instrucción preparada en particular. Si no se utiliza un plan en caché, se genera un plan de ejecución nuevo en cada visita a la instrucción, y los valores de los parámetros actuales (es decir, los valores de las variables de PL/pgSQL) se pueden utilizar para optimizar el plan seleccionado. Si la instrucción no tiene parámetros, o se ejecuta muchas veces, el gestor SPI considerará la creación de un plan genérico que no dependa de valores de parámetros específicos, y lo almacenará en caché para su reutilización. Normalmente, esto ocurrirá solo si el plan de ejecución no es muy sensible a los valores de las variables de PL/pgSQL a las que se hace referencia en él. Si lo es, generar un plan cada vez es una ventaja neta. Consulta la PREPARE para obtener más información sobre el comportamiento de las instrucciones preparadas.
Debido a que PL/pgSQL guarda las instrucciones preparadas y a veces los planes
de ejecución de esta manera, los comandos SQL que aparecen directamente en una función
PL/pgSQL deben hacer referencia a las mismas tablas y columnas en cada ejecución;
es decir, no puedes utilizar un parámetro como nombre de una tabla o columna en un comando SQL. Para evitar
esta restricción, puedes construir comandos dinámicos utilizando la instrucción EXECUTE
de PL/pgSQL, a costa de realizar un nuevo análisis sintáctico y construir un nuevo
plan de ejecución en cada ejecución.
La naturaleza mutable de las variables de registro presenta otro problema en este sentido. Cuando se utilizan
campos de una variable de registro en expresiones o instrucciones, los tipos de datos de los campos no deben
cambiar de una llamada de la función a la siguiente, ya que cada expresión se analizará utilizando el tipo de
datos que está presente cuando se alcanza la expresión por primera vez. Se puede utilizar EXECUTE
para evitar este problema cuando sea necesario.
Si la misma función se utiliza como disparador para más de una tabla, PL/pgSQL
prepara y almacena en caché las instrucciones de forma independiente para cada una de esas tablas; es decir,
hay una caché para cada combinación de función disparadora y tabla, no solo para cada función. Esto alivia
algunos de los problemas con los tipos de datos variables; por ejemplo, una función disparadora podrá trabajar
correctamente con una columna llamada key incluso si resulta tener diferentes tipos en diferentes
tablas.
Del mismo modo, las funciones que tienen tipos de argumentos polimórficos tienen una caché de instrucciones independiente para cada combinación de tipos de argumentos reales para los que han sido invocadas, de modo que las diferencias en los tipos de datos no causen fallos inesperados.
El almacenamiento en caché de instrucciones a veces puede tener efectos sorprendentes en la interpretación de valores sensibles al tiempo. Por ejemplo, hay una diferencia entre lo que hacen estas dos funciones:
CREATE FUNCTION logfunc1(logtxt text) RETURNS void AS $$
BEGIN
INSERT INTO logtable VALUES (logtxt, 'now');
END;
$$ LANGUAGE plpgsql;
y:
CREATE FUNCTION logfunc2(logtxt text) RETURNS void AS $$
DECLARE
curtime timestamp;
BEGIN
curtime := 'now';
INSERT INTO logtable VALUES (logtxt, curtime);
END;
$$ LANGUAGE plpgsql;
En el caso de logfunc1, el analizador principal de PostgreSQL
sabe al analizar el INSERT que la cadena 'now' debe interpretarse como
timestamp, porque la columna de destino de logtable es de ese tipo. Por lo tanto,
'now' se convertirá en una constante timestamp cuando se analice el
INSERT, y luego se utilizará en todas las invocaciones de logfunc1 durante
la vida útil de la sesión. De más está decir que esto no es lo que el programador quería. Una mejor idea es
utilizar la función now() o current_timestamp.
En el caso de logfunc2, el analizador principal de PostgreSQL
no sabe en qué tipo debe convertirse 'now' y, por lo tanto, devuelve un valor de datos de
tipo text que contiene la cadena now. Durante la asignación subsiguiente a la
variable local curtime, el intérprete de PL/pgSQL convierte esta
cadena al tipo timestamp llamando a las funciones textout y
timestamp_in para la conversión. Así, la marca de tiempo calculada se actualiza en cada
ejecución como espera el programador. Aunque esto funcione como se espera, no es terriblemente eficiente, por
lo que el uso de la función now() seguiría siendo una mejor idea.