PostgreSQL Error Codes, set a custom error code - postgresql

I'm translating a Oracle trigger to Postgres; I have this so far translated in postgres
DROP TRIGGER IF EXISTS trg_test_biud ON mytable CASCADE;
CREATE OR REPLACE FUNCTION trigger_fct_trg_test_biud() RETURNS trigger AS $BODY$
DECLARE
id_ double precision := NULL;
hour_ varchar(10) := NULL;
BEGIN
/* INSERT */
IF TG_OP = 'INSERT' THEN
BEGIN
select nextval('myschema.id_audit_mytable_seq') into id_;
SELECT TO_CHAR(current_timestamp, 'HH24:MI:SS') INTO hour_;
INSERT INTO myschema.audit_mytable(id, id_mytable, user_name, event, myhour, hour, geometry)
VALUES (id_, NEW.code, NEW.user_name, 'INSERT', LOCALTIMESTAMP, hour_, NEW.GEOMETRY);
RETURN NEW;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION '%', 'Error when insert into audit_mytable: ' || sqlerrm USING ERRCODE = '-20000';
END;
END IF;
END
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_test_biud
BEFORE INSERT OR UPDATE OR DELETE ON myschema.mytable FOR EACH ROW
EXECUTE PROCEDURE trigger_fct_trg_test_biud();
When the exception is raised, I get this error:
ERROR: unrecognized exception condition «-20000»
SQL state: 42704
Does this has to do with the fact that in Oracle the 'custom error code' is a negative number? postgres does not recognize this? I checked this page, but it says nothing about negative numbers: https://www.postgresql.org/docs/current/static/errcodes-appendix.html

The Oracle number -20000 is not an SQLSTATE, but a proprietary error code.
You have to specify one of the 5-character SQLSTATEs defined in appendix A of the documentation.

Related

Postgres function with nested IF & ELSE statement error

I have this function where it looks up all the users and if any one does not exists do not create the function. What is wrong?
RETURNS event_trigger AS $$
DECLARE
audit_query TEXT;
r RECORD;
BEGIN
IF tg_tag IN ('CREATE TABLE', 'CREATE TABLE AS')
THEN
IF EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'test' or rolname = 'testa')
THEN
FOR r IN
SELECT * FROM pg_event_trigger_ddl_commands() AS ddl WHERE ddl.schema_name IN ('testb','testc','testd')
LOOP
INSERT INTO user_monitor.ddl_history (ddl_date, ddl_tag, object_name) VALUES (statement_timestamp(), tg_tag, r.object_identity);
END LOOP;
else
RAISE EXCEPTION 'Not all users found';
END IF;
END;
$$ LANGUAGE plpgsql
SQL Error [42601]: ERROR: syntax error at or near ";"
SQL Error [42601]: ERROR: missing "THEN" at end of SQL expression
Position: 286
An excerpt from the manual:
Event triggers are created using the command CREATE EVENT TRIGGER. In order to create an event trigger, you must first create a function with the special return type event_trigger.
This function need not (and may not) return a value; the return type serves merely as a signal that the function is to be invoked as an event trigger.
Emphasis on the second sentence.
You should probably replace your RETURN with RAISE EXCEPTION instead.
https://www.postgresql.org/docs/current/event-trigger-table-rewrite-example.html

Trigger in postgres to lock table with a list

I have an issue regarding a Triggers with PostgreSQL 10. Here is the situation.
I have a table called index_name which contain à column named index_ref. Into this field I have created a list of value as example: PBM PI, PBM PO, etc.
I would like to use this table to store valid name as reference for another table called gis_osm_places.
So, whenever someone try to insert a value not into the list then an exception message will pop-up to say: NOT ALLOWED COMMIT. PLEASE USE: (reference list)
Here are my tables:
Here is where I am with the trigger:
CREATE FUNCTION public.check_column_value()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
DECLARE
ref_allowed character varying;
BEGIN
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE'
THEN
IF NEW.index_a is not null OR NEW.index_a NOT IN (SELECT index_ref from public.index_name)
THEN
ref_allowed := (SELECT string_agg(index_ref,',') from public.index_name);
RAISE EXCEPTION 'NOT ALLOWED COMMIT. PLEASE USE : %',ref_allowed;
END IF;
RETURN NEW;
END IF;
END;
$BODY$;
ALTER FUNCTION public.check_column_value()
OWNER TO "postgres";
CREATE TRIGGER check_column_value
BEFORE INSERT
ON public.gis_osm_places
FOR EACH ROW
EXECUTE PROCEDURE public.check_column_value();
Actually nothing is happening, I mean I can add whatever I want without an error.
Any idea or upgrade of the code would be greatly appreciate.
Thanks in advance !
You do not need not and do not want a trigger for this. This functionality is build in. Just create a Foreign Key constraint on gis_osm_places referencing index_name.
alter table gis_osm_places
add constraint osm_places2index_fk
foreign key (index_a)
references index_name(index_ref);
Now drop your trigger and the trigger function. The downside being you do not get the message you have created. But you handle that in your exception processing in the your app.
I finally found what went wrong !
CREATE FUNCTION public.check_column_value()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
DECLARE
ref_allowed character varying;
BEGIN
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE'
THEN
IF NEW.index_a is not null AND NEW.index_a NOT IN (SELECT index_ref from public.index_name)
THEN
ref_allowed := (SELECT string_agg(index_ref,',') from public.index_name);
RAISE EXCEPTION 'NOT ALLOWED COMMIT. PLEASE USE : %',ref_allowed;
END IF;
RETURN NEW;
END IF;
END;
$BODY$;
ALTER FUNCTION public.check_column_value()
OWNER TO "postgres";
CREATE TRIGGER check_column_value
BEFORE INSERT OR UPDATE
ON public.gis_osm_places
FOR EACH ROW
EXECUTE PROCEDURE public.check_column_value();

how to create event trigger for create table or select into

i want create event trigger for create table or select into,
eg:
when create table xxxx must table name bigen with 'temp'
my code
CREATE OR REPLACE FUNCTION create_table_func()
RETURNS event_trigger
AS
$$
DECLARE
V_TABLE name := TG_TABLE_NAME;
BEGIN
if V_TABLE !~ '^temp'
then
RAISE EXCEPTION 'must bigen with temp';
end if;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE EVENT TRIGGER create_table_1 ON ddl_command_start
WHEN TAG IN ('SELECT INTO')
EXECUTE PROCEDURE create_table_func();
but when execute
select * into test11 from test_bak
[Err] ERROR: column "tg_table_name" does not exist
this is my code ,it's meet my needs
code:
CREATE OR REPLACE FUNCTION trg_create_table_func()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag in ('SELECT INTO','CREATE TABLE','CREATE TABLE AS')
LOOP
if obj.object_identity !~ 'public.temp_'
THEN
raise EXCEPTION 'The table name must begin with temp_';
end if;
END LOOP;
END;
$$;
CREATE EVENT TRIGGER trg_create_table ON ddl_command_end
WHEN TAG IN ('SELECT INTO','CREATE TABLE','CREATE TABLE AS')
EXECUTE PROCEDURE trg_create_table_func();
out recods
[Err] ERROR: The table name must begin with temp_
CONTEXT: PL/pgSQL function trg_create_table_func() line 10 at RAISE
it's cool ~
The special variable TG_TABLE_NAME is only supported in normal triggers, not in event triggers (there is not always an associated table!).
The documentation has a list of functions that can return context information in an event trigger.
You could use pg_event_trigger_ddl_commands() to get the information you need, but that only works in ddl_command_end event triggers. That should work for you; I don't see a reason why the trigger should not run at the end of the statement.

postgresql trigger with dblink doesn't return anything

I created a trigger to replicate inserts from a table 'mytable_db1' in database1 into the same table 'mytable_db2' on database2. Both databases are on the same server.
CREATE OR REPLACE FUNCTION trigger_osm_test_insert()
RETURNS trigger AS
$BODY$
BEGIN
PERFORM dblink_connect('db2', 'dbname=xxx port=5432 user=myusr password=xxx');
PERFORM dblink_exec('db2',
'insert into test.mytable_db2 (osm_id, name, name_eng, name_int, type, z_order, population, last_update, country, iso3, shape)
values ('||new.osm_id||', '''||new.name||''', '''||new.name_eng||''', '''||new.name_int||''', '''||new.type||''', '||new.z_order||',
'||new.population||', '''||new.last_update||''', '''||new.country||''', '''||new.iso3||''',
st_geometry((st_AsText('''||new.shape::text||'''))))');
PERFORM dblink_disconnect('db2');
RETURN new;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION trigger_osm_test_insert()
OWNER TO myusr;
CREATE TRIGGER osm_insert_test
AFTER INSERT
ON mytable_db1
FOR EACH ROW
EXECUTE PROCEDURE trigger_osm_test_insert();
However, when I make a test insert such as:
insert into test.mytable_db1 (name, shape) values ('test', '0101000020E6100000E0979950035F4A40404B2751B0861CC0');
The inserted row is inserted into mytable_db1, but the trigger seem not being working as I have nothing in mytable_db2. The insert doesn't give me any error message from the trigger.
I'm using postgresql 9.2.2. Both databases have dblink 1.0 installed as well as postgis 2.0.6.
Does anyone have suggestions on what I am doing wrong?
Thanks!
There is a better solution to do it
Here another example
-- Function: flux_tresorerie_historique_backup_row()
-- DROP FUNCTION flux_tresorerie_historique_backup_row();
CREATE OR REPLACE FUNCTION flux_tresorerie_historique_backup_row()
RETURNS trigger AS
$BODY$
BEGIN
perform dblink_connect('dbname=gtr_bd_archive user=postgres password=postgres');
perform dblink_exec('insert into flux_tresorerie_historique values('||
concat_ws(', ', quote_nullable(OLD.id_flux_historique),
quote_nullable(OLD.date_operation_flux),
quote_nullable(OLD.date_valeur_flux),
quote_nullable(OLD.date_rapprochement_flux),
quote_nullable(OLD.libelle_flux),
quote_nullable(OLD.montant_flux),
quote_nullable(OLD.contre_valeur_dzd),
quote_nullable(OLD.rib_compte_bancaire),
quote_nullable(OLD.frais_flux),
quote_nullable(OLD.sens_flux),
quote_nullable(OLD.statut_flux),
quote_nullable(OLD.code_devise),
quote_nullable(OLD.code_mode_paiement),
quote_nullable(OLD.code_agence),
quote_nullable(OLD.code_compte),
quote_nullable(OLD.code_banque),
quote_nullable(OLD.date_maj_flux),
quote_nullable(OLD.statut_frais),
quote_nullable(OLD.reference_flux),
quote_nullable(OLD.code_commission),
quote_nullable(OLD.id_flux)
)||');');
perform dblink_disconnect();
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION flux_tresorerie_historique_backup_row()
OWNER TO postgres;
if your Tables are identique you can use "format" like this
CREATE OR REPLACE FUNCTION flux_tresorerie_historique_backup_row()
RETURNS trigger AS
$func$
BEGIN
PERFORM dblink_connect('myserver'); -- name of foreign server
PERFORM dblink_exec( format(
$$
INSERT INTO flux_tresorerie_historique
SELECT (%L::flux_tresorerie_historique).*
$$
, OLD::text));
PERFORM dblink_disconnect();
RETURN NULL; -- only for AFTER trigger
END
$func$ LANGUAGE plpgsql;
The problem is that you are not passing all columns when doing the INSERT. Because of this, some columns in NEW are null, and because you use string concatenation, your whole INSERT INTO... string is null.
For example, this query returns NULL:
select NULL || 'some text';
You should check every column for nulless, in example so:
CREATE OR REPLACE FUNCTION trigger_osm_test_insert()
RETURNS trigger AS
$BODY$
DECLARE insert_statement TEXT;
BEGIN
IF NOT ARRAY['db2'] <# dblink_get_connections() OR dblink_get_connections() IS NULL THEN
PERFORM dblink_connect('db2', 'dbname=xxx port=5432 user=xxx password=xxx');
END IF;
insert_statement = format('insert into mytable_db2 (
osm_id, name, name_eng, name_int, type, z_order,
population, last_update, country, iso3, shape
)
values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
coalesce(new.osm_id::text,'NULL'),
coalesce(quote_literal(new.name), 'NULL'),
coalesce(quote_literal(new.name_eng), 'NULL'),
coalesce(new.name_int::text, 'NULL'),
coalesce(quote_literal(new.type),'NULL'),
coalesce(new.z_order::text,'NULL'),
coalesce(new.population::text,'NULL'),
coalesce(quote_literal(new.last_update::text),'NULL'),
coalesce(quote_literal(new.country),'NULL'),
coalesce(quote_literal(new.iso3::text), 'NULL'),
coalesce(quote_literal(new.shape::text),'NULL')
);
PERFORM dblink_exec('db2', insert_statement);
PERFORM dblink_disconnect('db2');
RETURN new;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
I'm not sure about the types of the columns, so check it. Inserting values which can be NULL is a little tricky...Please use this:
coalesce(new.osm_id::text,'NULL'),
For inserting integer values and this one:
coalesce(quote_literal(new.name), 'NULL'),
for inserting text, timestamp and geometries values. In the insert statement use always %s.
In addition to this, I done the following modifications to the function:
A check if the connection does already exist
The function st_geometry((st_AsText()) is not needed, as you already insert geometries in WKB format
I use the function format() instead of the concatenate operator ||

how to circumvent missing record type on insert

I'd like to make a copy of a row in one table addressed by a field in another table, like this:
CREATE OR REPLACE FUNCTION f_ins_up_vorb()
RETURNS TRIGGER AS $$
DECLARE
dienst dienst%ROWTYPE;
account record;
BEGIN
-- ...
EXECUTE format('SELECT * FROM %s WHERE id=$1',dienst.tabelle)
USING NEW.id INTO account;
EXECUTE 'INSERT INTO ' || dienst.tabelle || 'shadow SELECT ($1).*, now(), $2' USING account, jobid;
RETURN NEW;
END
$$ LANGUAGE plpgsql;
But this yields:
ERROR: record type has not been registered
CONTEXT: SQL statement "INSERT INTO accountadshadow SELECT ($1).*, now(), $2"
PL/pgSQL function f_ins_up_vorb() line 30 at EXECUTE statement
The tables addressed by dienst.tabelle have no common type but the target table (dienst.tabelle || 'shadow') is always a superset of the source table. So this should always work (and does work in a trigger function, where I use NEW, which seems to have a record type).
Is there any way around this?
Try something like:
CREATE OR REPLACE FUNCTION f_ins_up_vorb()
RETURNS TRIGGER AS $$
DECLARE
dienst dienst%ROWTYPE;
BEGIN
-- ...
EXECUTE 'INSERT INTO '||dienst.tabelle||'shadow
SELECT *, now(), $2
FROM '||dienst.tabelle||'
WHERE id=$1'
USING NEW.id, jobid;
RETURN NEW;
END
$$ LANGUAGE plpgsql;
If you are trying to create some kind of log trigger - read this page first.