Observação : o exemplo a seguir é um código fortemente simplificado, mas ilustra a tarefa em um exemplo reproduzível. Olhar para este exemplo pode fazer você pensar "por que fazer assim"? Na realidade, a tarefa completa é armazenar uma trilha de auditoria para várias tabelas, mas somente se determinadas condições forem atendidas. A condição é a mesma para cada tabela (cada uma delas compartilha algumas colunas como inserted
, updated
e assim por diante). Portanto, o código para armazenar uma trilha de auditoria é o mesmo para cada tabela. Mas as colunas reais a serem copiadas são diferentes a cada vez. Eu quis criar um gatilho que lidasse com isso dinamicamente para que eu não precisasse tocá-lo toda vez que o esquema fosse alterado.
Considere o seguinte exemplo de trabalho (pergunta abaixo). Isso demonstra um esquema simples em que cada atualização na tabela data
faz com que os valores antigos sejam movidos para data2
:
DROP TABLE IF EXISTS data CASCADE;
DROP TABLE IF EXISTS data2 CASCADE;
CREATE TABLE data (
id SERIAL,
name TEXT,
updated TIMESTAMP WITH TIME ZONE
);
CREATE TABLE data2 (
id SERIAL,
name TEXT,
updated TIMESTAMP WITH TIME ZONE
);
CREATE OR REPLACE FUNCTION update_trigger_func()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated = NOW();
INSERT INTO data2 VALUES (OLD.*);
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_trigger
BEFORE UPDATE ON data
FOR EACH ROW
EXECUTE PROCEDURE update_trigger_func();
SET client_min_messages TO 'debug';
INSERT INTO data (name) VALUES ('foo');
COMMIT; -- Make sure we get new timestamps from NOW()
SELECT * FROM ONLY data;
SELECT * FROM ONLY data2;
SELECT pg_sleep(1);
UPDATE data SET name = 'bar';
COMMIT; -- Make sure we get new timestamps from NOW()
SELECT * FROM ONLY data;
SELECT * FROM ONLY data2;
SELECT pg_sleep(1);
UPDATE data SET name = 'baz';
COMMIT; -- Make sure we get new timestamps from NOW()
SELECT * FROM ONLY data;
SELECT * FROM ONLY data2;
Observe que a função update_trigger_func
tem o nome da tabela "history" codificado como data2
na linha que diz:
INSERT INTO data2 VALUES (OLD.*);
Se data2
fosse um argumento, esta função poderia ser reutilizável para outras tabelas também. Mas até agora não consegui encontrar o encantamento certo. Eu tentei as duas versões a seguir até agora:
INSERT INTO TG_ARGV[0] VALUES (OLD.*);
Mas isso causa um erro de sintaxe:
psql:temptable.sql:28: ERROR: syntax error at or near "VALUES"
LINE 11: INSERT INTO TG_ARGV[0] VALUES (OLD.*);
Então, alternativamente, tentei com SQL dinâmico:
sql := 'INSERT INTO' || TG_ARGV[0] || 'VALUES (OLD.*)';
EXECUTE sql;
Mas isso falha porque a OLD
variável não está disponível no contexto de execução:
psql:temptable.sql:58: ERROR: missing FROM-clause entry for table "old"
LINE 1: INSERT INTO data2 VALUES (OLD.*)
^
QUERY: INSERT INTO data2 VALUES (OLD.*)
CONTEXT: PL/pgSQL function versioned_update() line 11 at EXECUTE
Dado que eu gostaria de usar essa função de gatilho em outras tabelas, não posso codificar os nomes das colunas. Como eu poderia conseguir isso?
Infelizmente, pesquisar no Google foi bastante difícil, pois o tipo de dados
OLD
édata
, e pesquisar por isso no Google não resultou em nada utilizável. Além disso, descobrir quedata
é um tipo composto foi fundamental para encontrar a solução.A questão é dupla:
A tabela não pode ser usada diretamente em uma
INSERT
instrução, pois é do tipotext
. Portanto, o SQL dinâmico deve ser usado. Então, em vez de escrever diretamenteo seguinte teve que ser usado:
A
OLD
variável não está disponível diretamente dentro de umaEXECUTE
instrução. Então, construindo a partir do ponto de bala anterior, isso não foi possível:em vez disso, os valores de
OLD
tiveram que ser passados com aUSING
palavra-chave:A solução final:
Isso nos permite escrever um gatilho que lida com eventos na
data
tabela e grava o histórico nahistory
tabela como: