For a security sensitive design, I'd like to disable DELETEs on certain tables.
The DELETE should merely set a deleted flag on a row (which would be then visible on a view, which would be used by the application layer).
As I understand a rule would generate additional queries - so a rule could not suppress the original query.
As illustration a toy example with a trigger (not yet tested):
-- data in this table should be 'undeletable'
CREATE table article (
id serial,
content text not null,
deleted boolean default false
)
-- some view that would only show articles, that are NOT deleted
...
-- toy trigger (not tested)
CREATE OR REPLACE FUNCTION suppress_article_delete()
RETURNS TRIGGER AS $sad$
BEGIN
IF (TG_OP = 'DELETE') THEN
UPDATE article SELECT id, content, TRUE;
-- NEW or NULL??
RETURN NEW;
END IF;
RETURN NULL;
END;
$sad$ LANGUAGE plpgsql;
What would be a good way to suppress a DELETE?
As I understand a rule would generate additional queries - so a rule could not suppress the original query.
Not really - it could be an INSTEAD rule:
CREATE RULE shoe_del_protect AS ON DELETE TO shoe DO INSTEAD NOTHING;
(an example on that same page of the manual).
Another way is to REVOKE delete privileges on the table in question and to create stored procedure(s) for deleting... and updating and inserting also probably.
Related
I have 3 tables:
Detail
Detail_Archive
Transaction_Results
Transaction_Results is a new table that I did not build and has a constraint that I could probably remove altogether, but I would believe that it might break some functions that I assume it was built for.
ALTER TABLE Transaction_Results
ADD Constraint Transaction_Results_Detail_DetailID_fkey FOREIGN KEY (DetailID)
REFERENCES Details (DetailID) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
Now in the past I simply copied and deleted records out of Detail and into Detail_Archive
What would be the most common way(s) to keep the constraint?
My idea would be: maybe making it null-able and setting the values I remove to null, perhaps moving them to an additional column referencing the archive version of the table and making that one null-able as well. In my code where I needed this I will have to deal with this problem but that isn't so bad for me.
So I took the brute force approach.
ALTER TABLE Transaction_Results DROP CONSTRAINT Transaction_Results_Detail_DetailID_fkey ;
ALTER TABLE Transaction_Results ALTER COLUMN DetailID DROP NOT NULL;
ALTER TABLE Transaction_Results ADD COLUMN DetailID_Archive integer NULL;
CREATE OR REPLACE FUNCTION process_Detail_delete() RETURNS TRIGGER AS $Transaction_Results_trigger$
BEGIN
--
-- Update a row in Transaction_Results to reflect the delete performed on Detail,
-- make use of the special variable TG_OP to work out the operation.
--
IF (TG_OP = 'DELETE') THEN
UPDATE Transaction_Results SET DetailID = NULL, DetailID_Archive = OLD.DetailID where DetailID = OLD.DetailID ;
RETURN OLD;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$Transaction_Results_trigger$ LANGUAGE plpgsql;
CREATE TRIGGER Transaction_Results_trigger
AFTER DELETE ON [Detail]
FOR EACH ROW EXECUTE PROCEDURE process_Detail_delete();
So I found this example:
create function eager.account_insert() returns trigger
security definer
language plpgsql
as $$
begin
insert into eager.account_balances(name) values(new.name);
return new;
end;
$$;
create trigger account_insert after insert on accounts
for each row execute procedure eager.account_insert();
The thing I can't understand: function eager.account_insert() does not take any arguments, however, it operates with variable new. It returns it, but should't it return trigger?
Also, this: insert into eager.account_balances(name), this is not some certain record chosen, what it this?
The new (and old when it's an update statement) is the RECORD you're inserting or updating. You can get columns from it and do whatever you want with them. Many times BEFORE INSERT triggers check for valid values etc.
The function must return a RECORD with the same columns as the table, or NULL if the insert should not happen (usually for INSTEAD triggers).
The insert statement is just a regular insert with one column specified of the table and the value is taken from the newly inserted RECORD's column name.
The documentation explains the triggers very well.
I am using PostgreSQL as my database for a project at work. We use triggers in quite a few places to either maintain computed columns, or tables that essentially act as a materialized view.
All this worked just fine when simply utilizing row level triggers to keep all this in sync. However when we wrote scripts to periodically import our customers data into the database, we ran into issues with either performance or problems with number of locks in a single transaction.
To alleviate this I wanted to create a statement-level trigger with access to the modified rows (inserted, updated or deleted). However as this is not possible I instead created a BEFORE statement-level trigger that would create a temporary table. Then an AFTER row-level trigger that would insert the changed data into the temporary table. At last an AFTER statement-level trigger that would read the changes and perform necessary updates, and then drop the temporary table.
All this works just fine, assuming that within the triggers, no one would re-trigger the same flow again (as the temporary table would then already exist).
However I then learned that when using foreign key constraints with ON DELETE SET NULL, it is simply implemented with a system trigger that sets the column to NULL. This of course is not a problem at all, except for the fact that when you have several foreign key constraints like this on a single table, all referencing the same table (let's just call this files). When deleting a row from the files table, all these system level triggers to handle the ON DELETE SET NULL clause all fire at the same time, that is in parallel. Which presents a serious issue for me.
How would I go about implementing something like this? Here is a short SQL script to illustrate the problem:
CREATE TABLE files (
id serial PRIMARY KEY,
"name" TEXT NOT NULL
);
CREATE TABLE profiles (
id serial PRIMARY KEY,
NAME TEXT NOT NULL,
cv_file_id INT REFERENCES files(id) ON DELETE SET NULL,
photo_file_id INT REFERENCES files(id) ON DELETE SET NULL
);
CREATE TABLE profile_audit (
profile_id INT NOT NULL,
modified_at timestamptz NOT NULL
);
CREATE FUNCTION pre_stmt_create_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
CREATE TEMPORARY TABLE tmp_modified_profiles (
id INT NOT NULL
) ON COMMIT DROP;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE FUNCTION insert_modified_profile_to_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
INSERT INTO tmp_modified_profiles(id) VALUES (NEW.id);
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE FUNCTION post_stmt_insert_rows_and_drop_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
INSERT INTO profile_audit (id, modified_at)
SELECT t.id, CURRENT_TIMESTAMP FROM tmp_modified_profiles t;
DROP TABLE tmp_modified_profiles;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER tr_create_working_table BEFORE UPDATE ON profiles FOR EACH STATEMENT EXECUTE PROCEDURE pre_stmt_create_temp_table();
CREATE TRIGGER tr_insert_row_to_working_table AFTER UPDATE ON profiles FOR EACH ROW EXECUTE PROCEDURE insert_modified_profile_to_temp_table();
CREATE TRIGGER tr_insert_modified_rows_and_drop_working_table AFTER UPDATE ON profiles FOR EACH STATEMENT EXECUTE PROCEDURE post_stmt_insert_rows_and_drop_temp_table();
INSERT INTO files ("name") VALUES ('photo.jpg'), ('my_cv.pdf');
INSERT INTO profiles ("name") VALUES ('John Doe');
DELETE FROM files WHERE "name" = 'photo.jpg';
It would be a serious hack, but meanwhile, until PostgreSQL 9.5 is out, I would try to use CONSTRAINT triggers deferred to the end of the transaction. I am not really sure this will work, but might be worth trying.
You could use a status column to track inserts and updates for your statement-level triggers.
In a BEFORE INSERT OR UPDATE row-level trigger:
SET NEW.status = TG_OP;
Now you can use statement-level AFTER triggers:
BEGIN
DO FUNNY THINGS
WHERE status = 'INSERT';
-- reset the status
UPDATE mytable
SET status = NULL
WHERE status = 'INSERT';
END;
However, if you want to deal with deletes as well, you'll need something like this in your row-level trigger:
INSERT INTO status_table (table_name, op, id) VALUES (TG_TABLE_NAME, TG_OP, OLD.id);
Then, in your statement-level AFTER trigger, you can go like:
BEGIN
DO FUNNY THINGS
WHERE id IN (SELECT id FROM status_table
WHERE table_name = TG_TABLE_NAME AND op = TG_OP); -- just an example
-- reset the status
DELETE FROM status_table
WHERE table_name = TG_TABLE_NAME AND op = TG_OP;
END;
i have a table "demand_details"
on update or delete i want to store values of each row in another table "demand_details_log"
my functions is as follows
CREATE OR REPLACE FUNCTION water_blogb() RETURNS trigger AS
$BODY$
BEGIN
IF (TG_OP='UPDATE') THEN
INSERT INTO demand_details_log VALUES ('U',now(),OLD.*);
RETURN NEW;
END IF;
IF (TG_OP='DELETE') THEN
INSERT INTO demand_details_log VALUES ('D',now(),OLD.*);
RETURN OLD;
END IF;
END;
$BODY$ LANGUAGE plpgsql
my trigger is as follows
CREATE TRIGGER water_btrg_b
AFTER UPDATE OR DELETE
ON demand_details
FOR EACH ROW
EXECUTE PROCEDURE water_blogb();
MY problem is the same trigger and functions works well on other table (by changing table,trigger and function name) but not working with demand table. I tried with "RAISE NOTICE 'working...'" in both in other table trigger gets fired but in demand table its not fired at all.
As you found, triggers are not inherited. This leads to some difficulties in managing triggers in inherited table structures. You may want to read up on some of the issues involved at http://ledgersmbdev.blogspot.com/2012/08/postgresql-or-modelling-part-3-table.html and http://ledgersmbdev.blogspot.com/2012/08/or-modelling-32-setsubset-modelling.html.
Now those do not address table partitioning directly which may be what you are trying to do here. I would recommend that you build in some additional tests that you can run to check and make sure that triggers are properly installed on all subtables. I would suggest taking a look at How to find inherited tables programatically in PostgreSQL? and also the pg_trigger table so that you can build a report of child tables which do not share the triggers of their parents.
I have two tables representing two different types of imagery. I am using PostGIS to represent the boundaries of those images. Here is a simplified example of those tables:
CREATE TABLE img_format_a (
id SERIAL PRIMARY KEY,
file_path VARCHAR(1000),
boundary GEOGRAPHY(POLYGON, 4326)
);
CREATE TABLE img_format_p (
id SERIAL PRIMARY KEY,
file_path VARCHAR(1000),
boundary GEOGRAPHY(POLYGON, 4326)
);
I also have a cross reference table, which I want to contain all the IDs of the images that overlap each other. Whenever an image of type "A" gets inserted into the database, I want to check to see whether it overlaps any of the existing imagery of type "P" (and vice versa) and insert corresponding entries into the img_a_img_p cross reference table. This table should represent a many-to-many relationship.
My first instinct is to write a trigger to manage thisimg_a_img_p table. I've never created a trigger before, so let me know if this is a silly thing to do, but it seems to make sense to me. So I create the following trigger:
CREATE TRIGGER update_a_p_cross_reference
AFTER INSERT OR DELETE OR UPDATE OF boundary
ON img_format_p FOR EACH ROW
EXECUTE PROCEDURE check_p_cross_reference();
The part where I am getting stuck is with writing the trigger function. My code is in Java and I see that there are tools like PL/pgSQL, but I'm not sure if that's what I should use or if I even need one of those special add-ons.
Essentially all I need the trigger to do is update the cross reference table each time a new image gets inserted into either img_format_a or img_format_p. When a new image is inserted, I would like to use a PostGIS function like ST_Intersects to determine whether the new image overlaps with any of the images in the other table. For each image pair where ST_INTERSECTS returns true, I would like to insert a new entry into img_a_img_p with the ID's of both images. Can someone help me figure out how to write this trigger function? Here is some pseudocode:
SELECT * FROM img_format_p P
WHERE ST_Intersects(A.boundary, P.boundary);
for each match in selection {
INSERT INTO img_a_img_p VALUES (A.id, P.id);
}
You could wrap the usual INSERT ... SELECT idiom in a PL/pgSQL function sort of like this:
create function check_p_cross_reference() returns trigger as
$$
begin
insert into img_a_img_p (img_a_id, img_p_id)
select a.id, p.id
from img_format_a, img_format_p
where p.id = NEW.id
and ST_Intersects(a.boundary, p.boundary);
return null;
end;
$$ language plpgsql;
Triggers have two extra variables, NEW and OLD:
NEW
Data type RECORD; variable holding the new database row for INSERT/UPDATE operations in row-level triggers. This variable is NULL in statement-level triggers and for DELETE operations.
OLD
Data type RECORD; variable holding the old database row for UPDATE/DELETE operations in row-level triggers. This variable is NULL in statement-level triggers and for INSERT operations.
So you can use NEW.id to access the new img_format_p value that's going in. You (currently) can't use the plain SQL language for triggers:
It is not currently possible to write a trigger function in the plain SQL function language.
but PL/pgSQL is pretty close. This would make sense as an AFTER INSERT trigger:
CREATE TRIGGER update_a_p_cross_reference
AFTER INSERT
ON img_format_p FOR EACH ROW
EXECUTE PROCEDURE check_p_cross_reference();
Deletes could be handled with a foreign key on img_a_img_p and a cascading delete. You could use your trigger for UPDATEs as well:
CREATE TRIGGER update_a_p_cross_reference
AFTER INSERT OR UPDATE OF boundary
ON img_format_p FOR EACH ROW
EXECUTE PROCEDURE check_p_cross_reference();
but you'd probably want to clear out the old entries before inserting the new ones with something like:
delete from img_a_img_p where img_p_id = NEW.id;
before the INSERT...SELECT statement.