Replacing trigger procedure without deleting triggers - postgresql

I have trigger procedure:
CREATE OR REPLACE FUNCTION my_func() RETURNS trigger AS
$BODY$
// Do something :-)
RAISE NOTICE 'Done';
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
It is used for auditing and every single table in my DB have trigger which calls the procedure.
What I'm trying to do is to remove "raise notice" line - seems to be simple, but it is not.. I tried to insert new procedure to database without the line, thinking that "create od REPLACE" would do the work, but there was still only old version in my DB.
I think that the problem is caused by triggers connected with the procedure.
Do I have to 1. remove all of the triggers (over 80 tables => 80 triggers :( ), 2. replace procedure with new version, 3. re-create triggers?
Or is there another way to make "force replace"?
DBMS: PostgreSQL 9.2.6

What I'm trying to do is to remove "raise notice" line - seems to be simple, but it is not.. I tried to insert new procedure to database without the line, thinking that "create od REPLACE" would do the work, but there was still only old version in my DB.
Nope. Either you've installed the new function in the wrong database, wrong schema, with a typo in the name, failed to commit the change or missed an error message.
I replace trigger functions frequently and they're no different to any other function - CREATE OR REPLACE works just fine.

Related

PostgreSQL: Creating a Trigger that tries to do work on a non existing table

as we start to migrate our Application from using Oracle to PostgreSQL we ran into the following problem:
A lot of our Oracle scripts create triggers that work on Oracle specific tables which dont exist in PostgreSQL. When running these scripts on the PG database they will not throw an error.
Only when the trigger is triggered an error is thrown.
Example code:
-- Invalid query under PostgreSQL
select * from v$mystat;
-- Create a view with the invalid query does not work (as expected)
create or replace view Invalid_View as
select * from v$mystat;
-- Create a test table
create table aaa_test_table (test timestamp);
-- Create a trigger with the invalid query does(!) work (not as expected)
create or replace trigger Invalid_Trigger
before insert
on aaa_test_table
begin
select * from v$mystat;
end;
-- Insert fails if the trigger exists
insert into aaa_test_table (test) values(sysdate);
-- Select from the test table
select * from aaa_test_table
order by test desc;
Is there a way to change this behavior to throw an error on trigger creation instead?
Kind Regards,
Hammerfels
Edit:
I was made aware, that we actually dont use basic PostgreSQL but EDB instead.
That would probably explain why the syntax for create trigger seems wrong.
I'm sorry for the confusion.
It will trigger an error, unless you have configured Postgres to postpone validation when creating functions.
Try issuing this before creating the trigger:
set check_function_bodies = on;
Creating the trigger should show
ERROR: syntax error at or near "trigger"
LINE 1: create or replace trigger Invalid_Trigger

Creating a trigger gives "function does not exist" error in PostgreSQL

I'm working on a project right now where I'm trying to build a trigger that updates a column "lastedit" (in the table Person), which is a timestamp storing when the last change was made to the table in the schema specified in the trigger creation (in this case Certifications).
Now, my problem is that when I try to create the trigger, after creating the function "update_lastedit()", it gives me an error saying that the function does not exist. I think I might have a mismatch in my function somewhere, but I cannot seem to find it.
Could any of you help me out? I'm running PostgreSQL 9.5.5. Please let me know if I need to give a more extensive explanation, this is my first question, so I might have overlooked something important.
My code for the trigger and the function are as follows:
CREATE OR REPLACE FUNCTION update_lastedit() RETURNS TRIGGER AS
$update_edit$
BEGIN
UPDATE ovsoftware.person
SET lastedit = now();
END;
$update_edit$
LANGUAGE plpgsql;
and
CREATE TRIGGER cert_edit_trigger
BEFORE INSERT OR UPDATE ON ovsoftware.certifications
FOR EACH ROW
EXECUTE PROCEDURE update_lastedit();
The exact error:
SQL fout:
ERROR: function update_lastedit() does not exist
In statement:
CREATE TRIGGER cert_edit_trigger
BEFORE INSERT OR UPDATE ON ovsoftware.certifications
FOR EACH ROW
EXECUTE PROCEDURE update_lastedit();
The solution was to use a fully qualified name for the function as follows: ovsoftware.update_lastedit(). I am not sure why that is the case, as I did not need to do so in other cases.
Either way, the scope apparently did not include the ovsoftware schema, leading to the error.

How can I send column values as the payload in a postgresql NOTIFY message?

If an entry in a table satisfies certain conditions, a NOTIFY is sent out. I want the payload to include the ID number and several other columns of information. Is there a postgres method to convert variables (OLD.ColumnID, etc) to strings?
using postgres 9.3
#klin is correct that NOTIFY doesn't support anything other than string literals. However there is a function pg_notify() which takes normal arguments to deal with exactly this situation. It's been around since at least 9.0 and that link is to the official documentation - always worth reading it carefully, there is a wealth of information there.
My guess is that the notify has to be done within a trigger function. Use a dynamic query, e.g.
execute format('notify channel, ''id: %s''', old.id);
The solution was to upgrade Postgres to a version that supported JSON.
Even postgresql 9.3 supports json. You could have just used row_to_json(payload)::text
Sorry for the long answer, i just cant walk away without reacting to the other answers too.
The format version fails in many ways. Before EXECUTE, you shoud prepare the plan. The "pseudo command" does not fits the syntax of execute which is
EXECUTE somepreparedplanname (parameter1, ...)
The %s in format is again too bad, this way you can summon sql injection attacks. When constructing a query with format, you need to use %L for literals %I for column/table/function/etc ids, and use %s almost never.
The other solution with the pg_notify function is correct. Try
LISTEN channel;
SELECT pg_notify('channel','Id: '|| pg_backend_pid ());
in psql command line.
So back to the original question: sdemurjian,
Its not clarified in the question, if you wants to use this notification thing in some trigger function. So here is an example (maybe not) for you (because im a little late. sorry for that too):
CREATE TABLE columns("columnID" oid, "columnData" text);
CREATE FUNCTION column_trigger_func() RETURNS TRIGGER AS
$$ BEGIN PERFORM pg_notify('columnchannel', 'Id: '||OLD."columnID");
RETURN NEW; END; $$ LANGUAGE plpgsql;
CREATE TRIGGER column_notify BEFORE UPDATE ON columns FOR EACH ROW
EXECUTE PROCEDURE column_trigger_func();
LISTEN columnchannel;
INSERT INTO columns VALUES(1,'testdata');
BEGIN; UPDATE columns SET "columnData" = 'success'; END;
BEGIN; UPDATE columns SET "columnData" = 'fail'; ROLLBACK;
Please note that in early postgres versions (any before 9), the notify command does not accepts any payload and there is no pg_notify function.
In 8.1 the trigger function stil works if you define it like
CREATE FUNCTION column_trigger_func() RETURNS TRIGGER AS
$$ BEGIN NOTIFY columnchannel; RETURN NEW; END; $$ LANGUAGE plpgsql;

increasing entry number automatically for every insert on the entry table

i tried using this code:
CREATE OR REPLACE FUNCTION eno_inc() RETURNS trigger AS $eno_inc$
BEGIN
NEW.eno := OLD.eno + 1;
END;
$eno_inc$
LANGUAGE plpgsql;
but anytime i run it generates this error:
ERROR: record "old" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
CONTEXT: PL/pgSQL function "eno_inc" line 4 at assignment
*** Error ***
ERROR: record "old" is not assigned yet
SQL state: 55000
P.S i am very new to SQL
You are missing a
RETURN NEW;
at the end.
The error
ERROR: record "old" is not assigned yet SQL state: 55000
does show that this MIGHT not be a trigger (OLD and NEW are only used in trigger context). You need to define a trigger for that to work. I suggest you read the docs about trigger functions.
As the comment by Steve C notes, make sure you don't just need a SERIAL (auto-increment in MySQL terms).
In addition to what #DrColossos already wrote ..
OLD and NEW are only defined in a trigger ON UPDATE. In a trigger on INSERT you would get this error message. In a trigger on DELETE you only have OLD at your disposal.
Also, for a trigger to work you need a trigger function plus a trigger. You may be missing that, too. Start by reading the excellent manual here.
But I suspect your problems are more basic than that. First of all, as #Steve commented, you are probably just looking for a serial column or a sequence and a DEFAULT to your already existing column.

debugging postgresql trigger

I have this Trigger in Postgresql that I can't just get to work (does nothing). For understanding, there's how I defined it:
CREATE TABLE documents (
...
modification_time timestamp with time zone DEFAULT now()
);
CREATE FUNCTION documents_update_mod_time() RETURNS trigger
AS $$
begin
new.modification_time := now();
return new;
end
$$
LANGUAGE plpgsql;
CREATE TRIGGER documents_modification_time
BEFORE INSERT OR UPDATE ON documents
FOR EACH ROW
EXECUTE PROCEDURE documents_update_mod_time();
Now to make it a bit more interesting.. How do you debug triggers?
Use the following code within a trigger function, then watch the 'messages' tab in pgAdmin3 or the output in psql:
RAISE NOTICE 'myplpgsqlval is currently %', myplpgsqlval; -- either this
RAISE EXCEPTION 'failed'; -- or that
To see which triggers actually get called, how many times etc, the following statement is the life-saver of choice:
EXPLAIN ANALYZE UPDATE table SET foo='bar'; -- shows the called triggers
Note that if your trigger is not getting called and you use inheritance, it may be that you've only defined a trigger on the parent table, whereas triggers are not inherited by child tables automatically.
To step through the function, you can use the debugger built into pgAdmin3, which on Windows is enabled by default; all you have to do is execute the code found in ...\8.3\share\contrib\pldbgapi.sql against the database you're debugging, restart pgAdmin3, right-click your trigger function, hit 'Set Breakpoint', and then execute a statement that would cause the trigger to fire, such as the UPDATE statement above.
Turns out I was using inheritance in the above problem and forgot to mention it. Now for everybody who might run into this as well, here's some debugging hints:
Use the following code to debug what a trigger is doing:
RAISE NOTICE 'test'; -- either this
RAISE EXCEPTION 'failed'; -- or that
To see which triggers actually get called, how many times etc, the following statement is the life-saver of choice:
EXPLAIN ANALYZE UPDATE table SET foo='bar'; -- shows the called triggers
Then there's the one thing I didn't know before: triggers only fire when updating the exact table they're defined on. If you use inheritance, you MUST define them on the child tables as well!
You can use 'raise notice' statements inside your trigger function to debug it. To debug the trigger not being called at all is another story.
If you add a 'raise exception' inside your trigger function, can you still do inserts/updates?
Also, if your update test occurs in the same transaction as your insert test, now() will be the same (since it's only calculated once per transaction) and therefore the update won't seem to do anything. If that's the case, either do them in separate transactions, or if this is a unit test and you can't do that, use clock_timestamp().
I have a unit test that depends on some time going by between transactions, so at the beginning of the unit test I have something like:
ALTER TABLE documents
ALTER COLUMN modification_time SET DEFAULT clock_timestamp();
Then in the trigger, use "set modification_time = default".
So normally it doesn't do the extra calculation, but during a unit test this allows me to do inserts with pg_sleep in between to simulate time passing and actually have that be reflected in the data.