Wrote this trigger 5-6 months ago, and it ran fine on the db I deployed it to. Just took it off the shelf, and pretty much dropped it on the same database which lives on a different box. Sucker isn't executing anymore.
Essentially, this trigger just copies the "before" row to an audit table. The audit table lives in a different db on the same server, and I link it back to the source database as a FOREIGN Table:
Audit db:
CREATE TABLE next_gen_permissions_change
(
id serial NOT NULL,
authorizable_type character varying(64),
authorizable_id integer,
grantee_id integer,
grantee_type character varying(255),
capability_id integer NOT NULL,
permission integer DEFAULT 0,
event_time timestamp without time zone,
event_type character varying(10)
)
GRANT ALL ON TABLE next_gen_permissions_change TO foo;
I create the foreign table in the "source" database:
CREATE EXTENSION postgres_fdw;
CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'localhost', dbname 'audit', port '5432');
CREATE USER MAPPING FOR foo SERVER myserver OPTIONS (user 'foo', password 'secret');
CREATE foreign TABLE next_gen_permissions_change
(
id serial NOT NULL,
authorizable_type character varying(64),
authorizable_id integer,
grantee_id integer,
grantee_type character varying(255),
capability_id integer NOT NULL,
permission integer DEFAULT 0,
event_time timestamp without time zone,
event_type character varying(10))
SERVER myserver;
GRANT ALL ON TABLE next_gen_permissions_change TO foo;
When I INSERT rows into source.next_gen_permissions_change, I see them show up in audit.next_gen_permissions_change...so I got that going for me.
Here's the function:
CREATE OR REPLACE FUNCTION audit_permissions()
RETURNS trigger AS
$BODY$
DECLARE
trigger_event_time timestamp;
BEGIN
trigger_event_time := now();
IF (TG_OP = 'INSERT') THEN
INSERT INTO next_gen_permissions_change(
id,
authorizable_type,
authorizable_id,
grantee_id,
grantee_type,
capability_id,
permission,
event_time,
event_type)
VALUES (NEW.id,
NEW.authorizable_type,
NEW.authorizable_id,
NEW.grantee_id,
NEW.grantee_type,
NEW.capability_id,
NEW.permission,
trigger_event_time,
'insert');
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
INSERT INTO next_gen_permissions_change(
id,
authorizable_type,
authorizable_id,
grantee_id,
grantee_type,
capability_id,
permission,
event_time,
event_type)
VALUES (OLD.id,
OLD.authorizable_type,
OLD.authorizable_id,
OLD.grantee_id,
OLD.grantee_type,
OLD.capability_id,
OLD.permission,
trigger_event_time,
'delete');
RETURN OLD;
END IF;
RETURN null;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
And now, I'm adding the function as a trigger:
CREATE TRIGGER next_gen_permissions_audit
AFTER INSERT OR DELETE
ON next_gen_permissions
FOR EACH ROW
EXECUTE PROCEDURE audit_permissions();
No errors, all seems well:
As you can see, I added RAISE NOTICE as well, so I could hopefully see this sucker executing in pgadmin.
Here are all of the properties of the trigger (parent window) and function (child window embedded inside the parent):
WHEN executing something like
UPDATE next_gen_permissions SET f1=f1
...I don't see anything in the messages.
Also,
EXPLAIN ANALYZE UPDATE next_gen_permissions SET f1=f1
doesn't return anything interesting:
Can anyone see what I'm doing wrong here? I'm not getting any error messages, and when I look at the logs, the UPDATE goes right through without ANY mention that the trigger is getting fired.
Thanks much.
Related
I am using Microsoft Access as a front-end to my PostgreSQL database. My workflow is pretty simple:
Create a linked table using the ODBC driver
Build a form using form wizard for data entry
Inserting data works really well if i submit the data directly on the table or using the form. However, i can update the data that was submitted directly to the table, but i cannot update the data submitted through the form as i get a write.conflict error.
I checked many previous answers and one of the issues was to do with the timestamp precision. This helped with updating the data submitted directly on the table as it didn't work before.
Now i just need to resolve updating data that was submitted using the form. I tried using Me.Dirty as follows:
Private Sub Form_Dirty(Cancel As Integer)
Me.Dirty = False
End Sub
That didn't work for me unfortunately. It really does look like something to do with the form as entering data using the table works perfectly. Is it how the form saves the data? How can i get it off editing mode? I really don't know and i tried various things.
I would really appreciate a hand on this as i have been on it for days and i can't resolve it.
Thank you.
Here is a simplified version of the code i used to create my table. I just have more VARCHAR and numeric columns in the table i am using.
I also created a logs table that would update if any changes are made to the main table. This logs table is populate via a trigger as shown in the code.
CREATE TABLE table_1 (
id INT PRIMARY KEY DEFAULT to_char(now(), 'YYMMDDHH24MI') :: INT,
column_1 VARCHAR(50),
column_2 VARCHAR(100),
column_3 BOOLEAN,
last_updated timestamp(0));
CREATE TABLE logs_table
(like table_1 EXCLUDING CONSTRAINTS,
operation char(10) not null,
date_operated timestamp(0) default current_timestamp
);
create function logs_function()
returns trigger as $$
BEGIN
insert into logs_table (id, column_1, column_2, column_3, last_updated, operation)
values (old.id, old.column_1, old.column_2, old.column_3, old.last_updated, TG_OP);
IF TG_OP = 'UPDATE'
THEN
new.last_updated := current_timestamp;
RETURN NEW;
ELSIF TG_OP = 'DELETE'
THEN
RETURN OLD;
END IF;
end;
$$ LANGUAGE plpgsql;
CREATE TRIGGER logs_trigger
BEFORE UPDATE OR DELETE ON table_1
FOR EACH ROW EXECUTE PROCEDURE logs_function();
In PostgreSQL I have this table... (there is a primary key in the most left side "timestamp02" which is not shown in this image, please don't bother, its not important for the purpose of this question)
in the table above, all columns are entered via queries, except the "time_index" which I would like to be filled automatically via a trigger each time each row is filled.
This is the code to create the same table (without any value) so everyone could create it using the Postgre SQL query panel.
CREATE TABLE table_ebscb_spa_log02
(
pcnum smallint,
timestamp02 timestamp with time zone NOT NULL DEFAULT now(),
fn_name character varying,
"time" time without time zone,
time_elapse character varying,
time_type character varying,
time_index real,
CONSTRAINT table_ebscb_spa_log02_pkey PRIMARY KEY (timestamp02)
)
WITH (
OIDS=FALSE
);
ALTER TABLE table_ebscb_spa_log02
OWNER TO postgres;
What I would like the trigger to do is:
INSERT a number in the "time_index" column based on the INSERTed values of the "fn_name" and "time_type" columns in each row.
If both ("fn_name" and "time_type") do a combination (eg. Check Mails - Start) that doesn't exist in any row before (above), then INSERT 1 in the "time_index" column,
Elif both ("fn_name" and "time_type") do a combination that does exist in some row before (above), then INSERT the number following the one before(above) in the "time_index" column.
(pls look at the example table image, this trigger will produce every red highlighted square on it)
I have watch many, PostgreSQL tutorial videos, read many manuals, including these
http://www.postgresql.org/docs/9.4/static/sql-createtrigger.html
http://www.postgresql.org/docs/9.4/static/plpgsql-trigger.html
without any result.
I have tried so far this to create the function:
CREATE OR REPLACE FUNCTION on_ai_myTable() RETURNS TRIGGER AS $$
DECLARE
t_ix real;
n int;
BEGIN
IF NEW.time_type = 'Start' THEN
SELECT t.time_index FROM table_ebscb_spa_log02 t WHERE t.fn_name = NEW.fn_name AND t.time_type = 'Start' ORDER BY t.timestamp02 DESC LIMIT 1 INTO t_ix;
GET DIAGNOSTICS n = ROW_COUNT;
IF (n = 0) THEN
t_ix = 1;
ELSE
t_ix = t_ix + 1;
END IF;
END IF;
NEW.time_index = t_ix;
return NEW;
END
$$
LANGUAGE plpgsql;
And this to create the query:
CREATE TRIGGER on_ai_myTable
AFTER INSERT ON table_ebscb_spa_log02
FOR EACH ROW
EXECUTE PROCEDURE on_ai_myTable();
Then when I manually insert the values in the table, nothing change (no error message) time_index column just remain empty, what am I doing wrong???
Please some good PostgreSQL fellow programmer could give me a hand, I really have come to a death point in this task, I have any more ideas.
Thanks in advance
In an AFTER INSERT trigger, any changes you make to NEW.time_index will be ignored. The record is already inserted at this point; it's too late to modify it.
Create the trigger as BEFORE INSERT instead.
I have the following table:
CREATE TABLE myid
(
nid bigserial NOT NULL,
myid character varying NOT NULL,
CONSTRAINT myid_pkey PRIMARY KEY (myid )
)
Now, I want to add records to this table with the following function:
CREATE FUNCTION getmyid(_myid character varying)
RETURNS bigint AS
$BODY$ --version 1.1 2015-03-04 08:16
DECLARE
p_nid bigint;
BEGIN
SELECT nid INTO p_nid FROM myid WHERE myid=_myid FOR UPDATE;
IF NOT FOUND THEN
INSERT INTO myid(myid) VALUES(_myid) RETURNING nid INTO p_nid;
END IF;
RETURN p_nid;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Generally it works fine, but under high load, this function sometimes fails with "duplicate key value violates unique constraint "myid_pkey";
This function is called from trigger on insert on another table, and inserts are called within transaction. Isolation level is set for READ COMMITED, postgres 9.1 on Debian Wheezy.
What I'm doing wrong ?
I see following way how it happens.
Two processes(threads) call the function simultaneously with the same myid.
Both threads successfully execute SELECT nid INTO .. query, and see - there is no such myid in table now.
Both threads go into IF NOT FOUND THEN
Thread 1 executes INSERT INTO myid(myid) and commits transaction with no errors
Thread 2 executes INSERT INTO myid(myid) and fails, because same myid value already exists in table (PRIMARY KEY constraint).
Why Thread 2 sees other transaction committed data in own transaction ?
Because of 'non-repeatable read' phenomena, which is possible with READ COMMITTED isolation (http://www.postgresql.org/docs/9.2/static/transaction-iso.html).
I am writing a trigger in plpgsql for Postgres 9.1. I need to be able to capture the column names that were issued in the SET clause of an UPDATE so I can record the specified action in an audit table. The examples in the Postgres documentation are simple and inadequate for my needs. I have searched the internet for days and I am unable to find any other examples that try to achieve what I want to do here.
I am on a tight schedule to resolve this soon. I don't know Tcl so pl/Tcl is out of the question for me at this point. pl/Perl may work but I don't know where to start with it. Also I wanted to find a way to accomplish this in pl/pgsql if at all possible for portability and maintenance. If someone can recommend a pl/Perl solution to this I would be grateful.
Here is the table structure of the target table that will be audited:
Note: There are many other columns in the record table but I have not listed them here in order to keep things simple. But the trigger should be able to record changes to any of the columns in the row.
CREATE TABLE record (
record_id integer NOT NULL PRIMARY KEY,
lastname text,
frstname text,
dob date,
created timestamp default NOW(),
created_by integer,
inactive boolean default false
);
create sequence record_record_id_seq;
alter table record alter record_id set default nextval('record_record_id_seq');
Here is my audit table:
CREATE TABLE record_audit (
id integer NOT NULL PRIMARY KEY,
operation char(1) NOT NULL, -- U, I or D
source_column text,
source_id integer,
old_value text,
new_value text,
created_date timestamp default now(),
created_by integer
);
create sequence record_audit_id_seq;
alter table record_audit alter id set default nextval('record_audit_id_seq');
My goal is to record INSERTS and UPDATES to the record table in the record_audit table that will detail not only what the target record_id was (source_id) that was updated and what column was updated (source_column), but also the old_value and the new_value of the column.
I understand that the column values will have to be CAST() to a type of text. I believe I can access the old_value and new_value by accessing NEW and OLD but I am having difficulty figuring out how to obtain the column names used in the SET clause of the UPDATE query. I need the trigger to add a new record to the record_audit table for every column specified in the SET clause. Note, there are not DELETE actions as records are simply UPDATED to inactive = 't' (and thus recorded in the audit table)
Here is my trigger so far (obviously incomplete). Please forgive me, I am learning pl/pgsql as I go.
-- Trigger function for record_audit table
CREATE OR REPLACE FUNCTION audit_record() RETURNS TRIGER AS $$
DECLARE
insert_table text;
ref_col text; --how to get the referenced column name??
BEGIN
--
-- Create a new row in record_audit depending on the operation (TG_OP)
--
IF (TG_OP = 'INSERT') THEN
-- old_value and new_value are meaningless for INSERTs. Just record the new ID.
INSERT INTO record_audit
(operation,source_id,created_by)
VALUES
('I', NEW.record_id, NEW.created_by);
ELSIF (TG_OP = 'UPDATE') THEN
FOR i in 1 .. TG_ARGV[0] LOOP
ref_col := TG_ARGV[i].column; -- I know .column doesn't exist but what to use?
INSERT INTO record_audit
(operation, source_column, source_id, old_value, new_value, created_by)
VALUES
('U', ref_col, NEW.record_id, OLD.ref_col, NEW.ref_col, NEW.created_by);
END LOOP;
END IF;
RETURN NULL; -- result is ignored anyway since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER record_audit_trig
AFTER INSERT OR UPDATE on record
FOR EACH ROW EXECUTE PROCEDURE audit_record();
Thanks for reading this long and winding question!
you cannot to get this information - not in PL level - probably it is possible in C.
Good enough solution is based on changed fields in records NEW and OLD. You can get list of fields from system tables ~ are related to table that is joined to trigger.
I know there're ways to access to query type, table names, oids etc. in trigger definitions:
http://www.postgresql.org/docs/8.3/static/plpgsql-trigger.html
Anyway, is there any chance that i run an UPDATE on a row, which started the trigger without actualy need to marking each row in the table by unique id?
e.g. if i have table for storing logs, i don't need any unique id here... and as the time goes there may actually appear some rows, which will be equal.
CREATE TABLE users_log
(
uid bigint NOT NULL,
event smallint NOT NULL,
source character varying,
event_time timestamp with time zone
)
I know, that due to microseconds precision in "timestamp with time zone" data type is this situation nearly impossible, but not really impossible...
So how shall i write the query in trigger to be able to UPDATE just the inserted row?
$BODY$BEGIN
UPDATE "users_log" SET "event_time" = now(); -- this updates all rows
-- WHERE "id" = NEW.id; - this is what i don't want
RETURN NEW;
END;$BODY$
You just use in your trigger:
$BODY$BEGIN
NEW.event_time = now();
RETURN NEW;
END;$BODY$
And it just works — no need for additional update. You need to declare this trigger as BEFORE TRIGGER though.