jackc / tern

The SQL Fan's Migrator

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[question] migration multiples schemas

johndiego opened this issue · comments

I have studied issues as far as I could, but I haven't found an answer to my question, sorry in advance if I was inattentive.
How can I implement the following task:
I have with make file

run_command = tern --migrations ./sqls migrate
criacao_arquivo = tern --migrations ./sqls new $(1)

check_schema:
	@if [ -z "$(schema)" ]; then \
		echo "Erro: o parâmetro 'schema' é obrigatório."; \
		exit 1; \
	fi
check_nome:
	@if [ -z "$(nome)" ]; then \
		echo "Erro: o parâmetro 'nome' é obrigatório."; \
		exit 1; \
	fi

up: check_schema
	env SCHEMA_APP=$(schema) $(call run_command);

install: check_schema
	env SCHEMA_APP=$(schema) tern code install ./sqls --config ./tern.conf;

novo: check_nome
	$(call criacao_arquivo, $(nome))
	LAST_FILE=$$(ls -t sqls | head -n1); \
	touch "tests/test_$$LAST_FILE"; \
	echo "SET search_path TO  {{.prefix}};\n---- create above / drop below ----" > "sqls/$$LAST_FILE" ;\
	echo "BEGIN;\nSELECT plan(1);\nSELECT pass( 'ESCREVA O TESTE' );\nSELECT * FROM finish();\nROLLBACK" > "tests/test_$$LAST_FILE"  ;\
	chmod 777 . -R
runtest:
	PGOPTIONS=--search_path=coreapp,pgtap,public pg_prove -U ${POSTGRES_USER} tests/*

my structure
image

my install file

-- install.sql
create schema if not exists {{ env "SCHEMA_APP" }};
SET search_path TO  {{ env "SCHEMA_APP" }};
CREATE TABLE IF NOT EXISTS  logger (
    id bigserial primary key,
    table_name text not null,
	usuario_sessao jsonb default '{}'::jsonb,
	create_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    action TEXT NOT NULL CHECK (action IN ('I','D','U','T')),
    row_data jsonb,
	op_date text default '',
	before_row jsonb,
	after_row jsonb,
	client_query text
);
CREATE OR REPLACE FUNCTION INSERT_LOGGER() RETURNS TRIGGER AS $$
		DECLARE
			audit_row logger;
			excluded_cols text[] = ARRAY[]::text[];
			new_r jsonb;
			old_r jsonb;
		BEGIN
			IF TG_WHEN <> 'AFTER' THEN
				RAISE EXCEPTION 'only run as an AFTER trigger';
			END IF;
			audit_row.id = nextval('logger_id_seq');
			audit_row.table_name = TG_TABLE_NAME::text;
			audit_row.op_date = TO_CHAR(current_timestamp,'dd-mm-yyyy HH24:MI');
			audit_row.usuario_sessao = COALESCE (current_setting('user.request', 't'),'{}')::JSONB;
			audit_row.create_at = current_timestamp;
			audit_row.action = substring(TG_OP,1,1);
			audit_row.client_query = current_query();
			IF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW' ) THEN
				audit_row.row_data = to_jsonb(NEW);
				audit_row.before_row = to_jsonb('{}'::json);
				audit_row.after_row = to_jsonb(NEW);
			ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW' ) THEN
				audit_row.row_data = to_jsonb(OLD) - 'data_atualizacao';
				audit_row.before_row = to_jsonb(OLD) - 'data_atualizacao';
				audit_row.after_row = to_jsonb('{}'::json);
			ELSIF (TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW') THEN
                old_r = to_jsonb(OLD)  - 'data_atualizacao';
				new_r = to_jsonb(NEW)  - 'data_atualizacao';
				audit_row.row_data = old_r;
				SELECT  jsonb_object_agg(new_t.key, new_t.value) INTO  audit_row.after_row
				FROM jsonb_each(old_r) as old_t	JOIN jsonb_each(new_r) as new_t  ON (old_t.key = new_t.key AND old_t.value <> new_t.value);
				SELECT  jsonb_object_agg(new_t.key, old_t.value) INTO  audit_row.before_row
				FROM jsonb_each(old_r) as old_t	JOIN jsonb_each(audit_row.after_row) as new_t  ON (old_t.key = new_t.key);
			END IF;
		IF (audit_row.before_row is null AND audit_row.after_row is null ) THEN
			RETURN NULL;
		END IF;
		INSERT INTO logger VALUES (audit_row.*);
		RETURN NULL;
		END;
$$ LANGUAGE plpgsql;
CREATE OR  REPLACE function ddl_logger_commando() returns event_trigger
language plpgsql volatile as $$
declare
    obj record;I have studied issues as far as I could, but I haven't found an answer to my question, sorry in advance if I was inattentive.
How can I implement the following task:
I have with make file
Is it possible with tern now?
my intencion is  when run migration run just schema enviroment 
i know happing when run in first schema ok , i am new schema display errors same setting SET search_path TO  {{ env "SCHEMA_APP" }};
how fix this problem , i 'm  sorry my english.
Thank you very much for your brilhant efforts.
    nome_tabela TEXT :='';
	nome_trigger TEXT :='';
begin
    FOR obj in select * from pg_event_trigger_ddl_commands()
    loop
        if obj.object_type = 'table' THEN
		   nome_tabela := SPLIT_PART(obj.object_identity,'.',2);
		   nome_trigger :=  'logger_auditoria_' || nome_tabela;
			IF NOT EXISTS ( SELECT 1 FROM pg_trigger WHERE tgname = nome_trigger  AND tgrelid = nome_tabela::regclass ) THEN
                EXECUTE format('CREATE TRIGGER %I AFTER INSERT OR UPDATE OR DELETE ON %I FOR EACH ROW EXECUTE FUNCTION INSERT_LOGGER()', nome_trigger, nome_tabela);
            END IF;
        end if;
    end loop;
end
$$;
create event TRIGGER ddl_logger_commando on DDL_COMMAND_END when tag in('CREATE TABLE') execute procedure ddl_logger_commando();
Gravar_google-chrome_20240606191548.mp4

Is it possible with tern now?
my intencion is when run migration run just schema enviroment
i know happing when run in first schema ok , i am new schema display errors same setting SET search_path TO {{ env "SCHEMA_APP" }};
how fix this problem , I've been stuck with this problem for seven days sorry my english.
Thank you very much for your brilhant efforts.
Is it possible with tern now?

p.s. thank you very much for your efforts

Sorry, I'm not sure exactly what you are trying to go. I gather it is something with schemas, but I don't know what the problem you are encountering is. As far as I know, tern should support any schema related things you want to do.

I suggest narrowing the problem down to a minimal reproduction case with only tern, instead of also using make.

@jackc thanks for reply.
I have

-- install.sql
create schema if not exists {{.prefix}};
SET search_path TO {{.prefix}};
CREATE TABLE IF NOT EXISTS  logger (
    id bigserial primary key,
    table_name text not null,
	usuario_sessao jsonb default '{}'::jsonb,
	create_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    action TEXT NOT NULL CHECK (action IN ('I','D','U','T')),
    row_data jsonb,
	op_date text default '',
	before_row jsonb,
	after_row jsonb,
	client_query text
);
CREATE OR REPLACE FUNCTION INSERT_LOGGER() RETURNS TRIGGER AS $$
		DECLARE
			audit_row logger;
			excluded_cols text[] = ARRAY[]::text[];
			new_r jsonb;
			old_r jsonb;
		BEGIN
			IF TG_WHEN <> 'AFTER' THEN
				RAISE EXCEPTION 'only run as an AFTER trigger';
			END IF;
			audit_row.id = nextval('logger_id_seq');
			audit_row.table_name = TG_TABLE_NAME::text;
			audit_row.op_date = TO_CHAR(current_timestamp,'dd-mm-yyyy HH24:MI');
			audit_row.usuario_sessao = COALESCE (current_setting('user.request', 't'),'{}')::JSONB;
			audit_row.create_at = current_timestamp;
			audit_row.action = substring(TG_OP,1,1);
			audit_row.client_query = current_query();
			IF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW' ) THEN
				audit_row.row_data = to_jsonb(NEW);
				audit_row.before_row = to_jsonb('{}'::json);
				audit_row.after_row = to_jsonb(NEW);
			ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW' ) THEN
				audit_row.row_data = to_jsonb(OLD) - 'data_atualizacao';
				audit_row.before_row = to_jsonb(OLD) - 'data_atualizacao';
				audit_row.after_row = to_jsonb('{}'::json);
			ELSIF (TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW') THEN
                old_r = to_jsonb(OLD)  - 'data_atualizacao';
				new_r = to_jsonb(NEW)  - 'data_atualizacao';
				audit_row.row_data = old_r;
				SELECT  jsonb_object_agg(new_t.key, new_t.value) INTO  audit_row.after_row
				FROM jsonb_each(old_r) as old_t	JOIN jsonb_each(new_r) as new_t  ON (old_t.key = new_t.key AND old_t.value <> new_t.value);
				SELECT  jsonb_object_agg(new_t.key, old_t.value) INTO  audit_row.before_row
				FROM jsonb_each(old_r) as old_t	JOIN jsonb_each(audit_row.after_row) as new_t  ON (old_t.key = new_t.key);
			END IF;
		IF (audit_row.before_row is null AND audit_row.after_row is null ) THEN
			RETURN NULL;
		END IF;
		INSERT INTO logger VALUES (audit_row.*);
		RETURN NULL;
		END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION ddl_logger_commando() RETURNS event_trigger
LANGUAGE plpgsql VOLATILE AS $$
DECLARE
    obj RECORD;
    nome_tabela TEXT := '';
    nome_trigger TEXT := '';
BEGIN
    FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
    LOOP
        IF obj.object_type = 'table' THEN
            nome_tabela := SPLIT_PART(obj.object_identity, '.', 2);
            nome_trigger := 'logger_auditoria_' || nome_tabela;
            IF NOT EXISTS (
                SELECT 1 FROM pg_trigger 
                WHERE tgname = nome_trigger AND tgrelid = nome_tabela::regclass
            ) THEN
                EXECUTE format('CREATE TRIGGER %I AFTER INSERT OR UPDATE OR DELETE ON %I FOR EACH ROW EXECUTE FUNCTION INSERT_LOGGER()', nome_trigger, nome_tabela);
            END IF;
        END IF;
    END LOOP;
END
$$;

CREATE EVENT TRIGGER ddl_logger_commando 
ON DDL_COMMAND_END 
WHEN TAG IN ('CREATE TABLE') 
EXECUTE PROCEDURE ddl_logger_commando();

my conf is


[database]
host =  {{env "POSTGRES_HOST"}}
port = {{env "POSTGRES_PORT"}}
database = {{env "POSTGRES_DB"}}
password = {{env "POSTGRES_PASSWORD"}}
user = {{env "POSTGRES_USER"}}
version_table = {{.prefix}}._schema_version

[data]
prefix = schema2

i change file for

[database]
host =  {{env "POSTGRES_HOST"}}
port = {{env "POSTGRES_PORT"}}
database = {{env "POSTGRES_DB"}}
password = {{env "POSTGRES_PASSWORD"}}
user = {{env "POSTGRES_USER"}}
version_table = {{.prefix}}._schema_version

[data]
prefix = schema3

Run script in schema schema3 but if change schema for schema3
ERROR: function insert_logger() does not exist (SQLSTATE 42883):

I don't think it's the tern problem, could you help me or point out the way to solve the problem?
p.s. thank you very much for your efforts

I'm not sure. You might need to include the schema name in your create trigger statement. It also might help to try running your code statement by statement in psql. Might get more insight there.

I found the problem thanks for repply.