¿Cómo acceder al campo NUEVO u ANTIGUO dado solo el nombre del campo?

8

Estoy escribiendo un activador de validación. El disparador debe validar que la suma de una matriz es igual a otro campo. Como tengo muchas instancias de esta validación, quiero escribir un solo procedimiento y crear múltiples disparadores, cada uno con un conjunto diferente de campos para verificar.

Por ejemplo, tengo el siguiente esquema:

CREATE TABLE daily_reports(
     start_on date
   , show_id uuid
   , primary key(start_on, show_id)

     -- _graph are hourly values, while _count is total for the report
   , impressions_count bigint not null
   , impressions_graph bigint[] not null

   -- interactions_count, interactions_graph
   -- twitter_interactions_count, twitter_interactions_graph
);

La validación debe confirmar eso impressions_count = sum(impressions_graph).

Estoy atascado porque no sé cómo acceder dinámicamente a un campo desde NEWplpgsql:

CREATE FUNCTION validate_sum_of_array_equals_other() RETURNS TRIGGER AS $$
DECLARE
  total bigint;
  array_sum bigint;
BEGIN
  -- TG_NARGS = 2
  -- TG_ARGV[0] = 'impressions_count'
  -- TG_ARGV[1] = 'impressions_graph'

  -- How to access impressions_count and impressions_graph from NEW?
  RETURN NEW;
END
$$ LANGUAGE plpgsql;

CREATE TRIGGER validate_daily_reports_impressions
ON daily_reports BEFORE INSERT OR UPDATE
FOR EACH ROW EXECUTE
  validate_sum_of_array_equals_other('impressions_count', 'impressions_graph');

Intenté ejecutar comandos dinámicos haciendo EXECUTE 'SELECT $1 FROM NEW' INTO total USING TG_ARGV[0], pero PL / PGsql se queja de que NUEVO es una relación desconocida.

Me dirijo específicamente a PostgreSQL 9.1.

François Beausoleil
fuente
Cross publicado en la lista de correo general de PostgreSQL postgresql.org/message-id/…
François Beausoleil
AFAIK, la única forma de acceder dinámicamente a los campos NEWen este momento es usar hstore(NEW)y luego acceder a los campos como hstorevalores ingresados ​​por nombre de columna. Lo que apesta, porque entonces todos están emitidos texty si quieres trabajar con ellos en su tipo original, tienes que devolverlos. Alternativamente, puede escribir un activador en otro lenguaje de procedimiento como PL / Python que tenga un mejor soporte para el acceso dinámico a registros.
Craig Ringer
@ CraigRinger: Bueno, no. Eres demasiado pesimista hoy. Hay una manera con SQL dinámico.
Erwin Brandstetter

Respuestas:

14

En realidad, dado que NEWes un tipo compuesto bien definido, puede acceder a cualquier columna con notación de atributo simple y llanamente. El propio SQL no permite identificadores dinámicos (nombres de tabla o columna, etc.). Pero puede usar SQL dinámico conEXECUTE una función PL / pgSQL.

Manifestación

CREATE OR REPLACE FUNCTION trg_demo1()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col_value text;
   _col_name  text := quote_ident(TG_ARGV[0]);  -- escape identifier
BEGIN
   EXECUTE format('SELECT ($1).%s::text', _col_name)
   USING NEW
   INTO  _col_value;

   -- do something with _col_value ...

   RAISE NOTICE 'It works. The value of NEW.% is >>%<<.', _col_name, _col_value;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

El reparto a textes opcional. Usándolo, porque funciona universalmente. Si conoce el tipo, puede trabajar sin lanzar ...

Usando format()con %s, porque el identificador ya se escapó en ese punto.
De lo contrario, el uso format()de %Ipara proteger contra la inyección de SQL.

Alternativamente , en Postgres 9.3 o posterior, puede convertir NEWa JSON con to_json()y acceder a las columnas como claves:

CREATE OR REPLACE FUNCTION trg_demo2()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col_value text := to_json(NEW) ->> TG_ARGV[0];  -- no need to escape identifier
BEGIN
   RAISE NOTICE 'It works. The value of NEW.% is >>%<<.', TG_ARGV[0], _col_value;
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

Dado que el nombre de la columna no se concatena en una cadena SQL, la inyección SQL no es posible y no es necesario escapar del nombre.

db <> violín aquí (con en EXCEPTIONlugar de NOTICEhacer visible el efecto).

Relacionado:

Erwin Brandstetter
fuente