El truco con PREPARE
no funciona, ya que no toma una * cadena de texto* (un valor) como CREATE FUNCTION
lo hace, pero una declaración válida (código).
Para convertir datos en código ejecutable necesita usar SQL dinámico, es decir, EXECUTE
en una función plpgsql o DO
declaración. Esto funciona sin problemas siempre que el tipo de retorno no dependa del resultado de la primera función myresult()
. De lo contrario, volverá a atrapar 22 como se describe en mi respuesta anterior:
- Cómo ejecutar un resultado de cadena de un procedimiento almacenado en postgres
La parte crucial es declarar el tipo de devolución (tipo de fila en este caso) de alguna manera. Puedes crear una TABLE
, TEMP TABLE
o TYPE
con el propósito. O puede usar una declaración preparada o un cursor de referencia.
Solución con declaración preparada
Has estado muy cerca. La pieza que falta del rompecabezas es preparar la consulta generada con SQL dinámico .
Función para preparar declaraciones dinámicamente
Cree esta función una vez . Es una versión optimizada y segura de su función myresult()
:
CREATE OR REPLACE FUNCTION f_prep_query (_tbl regclass, _prefix text)
RETURNS void AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM pg_prepared_statements WHERE name = 'stmt_dyn') THEN
DEALLOCATE stmt_dyn;
END IF; -- you my or may not need this safety check
EXECUTE (
SELECT 'PREPARE stmt_dyn AS SELECT '
|| string_agg(quote_ident(attname), ',' ORDER BY attname)
|| ' FROM ' || _tbl
FROM pg_catalog.pg_attribute
WHERE attrelid = _tbl
AND attname LIKE _prefix || '%'
AND attnum > 0
AND NOT attisdropped
);
END
$func$ LANGUAGE plpgsql;
Yo uso regclass
para el parámetro de nombre de tabla _tbl
para que sea inequívoco y seguro contra SQLi. Detalles:
- Nombre de tabla como parámetro de función de PostgreSQL
El esquema de información no incluye la columna oid de los catálogos del sistema, así que cambié a pg_catalog.pg_attribute
en lugar de information_schema.columns
. Eso es más rápido, también. Hay ventajas y desventajas para esto:
- Cómo verificar si una tabla existe en un esquema dado
Si una sentencia preparada con el nombre stmt_dyn
ya existía, PREPARE
plantearía una excepción. Si eso es aceptable, elimine la marca en la vista del sistema pg_prepared_statements
y el siguiente DEALLOCATE
.
Son posibles algoritmos más sofisticados para administrar múltiples declaraciones preparadas por sesión, o tomar el nombre de la declaración preparada como parámetro adicional, o incluso usar un hash MD5 de la cadena de consulta como nombre, pero eso está más allá del alcance de esta pregunta.
Tenga en cuenta que PREPARE
opera fuera del alcance de las transacciones , una vez PREPARE
tiene éxito, la instrucción preparada existe durante la duración de la sesión. Si se aborta la transacción de envoltura, PREPARE
no se ve afectado ROLLBACK
no puedo eliminar declaraciones preparadas.
Ejecución dinámica de consultas
Dos consultas, pero solo una llamar al servidor. Y muy eficiente, también.
SELECT f_prep_query('tbl'::regclass, 'pre'::text);
EXECUTE stmt_dyn;
Más simple y mucho más eficiente para la mayoría de los casos de uso simples que crear una tabla temporal o un cursor y seleccionar/obtener desde eso (que serían otras opciones).
Violín SQL.