tags:

views:

45

answers:

2

I've got a fairly simple table which stores the records' authors in a text field as shown here:

CREATE TABLE "public"."test_tbl" (
  "index" SERIAL, 
  "testdate" DATE, 
  "pfr_author" TEXT DEFAULT "current_user"(), 
  CONSTRAINT "test_tbl_pkey" PRIMARY KEY("index");

The user will never see the index or pfr_author fields, but I'd like them to be able to UPDATE the testdate field or DELETE whole records if they have permission and if they are the author. i.e. if test_tbl.pfr_author = CURRENT_USER THEN permit the UPDATE OR DELETE, but if not then raise an error message such as "Sorry, you do not have permission to edit this record.".

I have not gone down the route of using a trigger as I figure that even if it is executed before row update the user-requested update will still take place afterwards regardless.

I've tried doing this through a rule, but end up with infinite recursion as I put an update command inside the rule. Is there some way to do this using rules alone or a combination of a rule and trigger?

Thanks very much for any help!

A: 

Use a row level BEFORE trigger on UPDATE and DELETE to do this. Just have it return NULL when the operation is not permitted and the operation will be skipped.

http://www.postgresql.org/docs/9.0/interactive/trigger-definition.html

m.m.
Thanks m.m but I think I need a bit more help. I built the trigger below but this seems to end up in an infinite recursion loop. Is it a matter of somehow breaking the loop after the update in the trigger has run?
Bannor
CREATE OR REPLACE FUNCTION "public"."test_tbl_trig_func" ()RETURNS trigger AS$body$BEGINIF (old.pfr_author = "current_user"() OR "current_user"() = 'postgres') THEN UPDATE test_tbl SET testdate = new . testdate WHERE "index" = new.index;ELSENULL;END IF;END;$body$LANGUAGE 'plpgsql'VOLATILECALLED ON NULL INPUTSECURITY INVOKERCOST 100;CREATE TRIGGER "test_tbl_trig" BEFORE UPDATE ON "public"."test_tbl" FOR EACH ROW EXECUTE PROCEDURE "public"."test_tbl_trig_func"();
Bannor
EXPLAIN ANALYSE UPDATE test_tbl SET testdate = CURRENT_DATE WHERE test_tbl."index" = 2; resulted in: EXPLAIN ANALYSE UPDATE test_tbl SET testdate = CURRENT_DATE WHERE test_tbl."index" = 2;...
Bannor
Sorry, I didn't read your answer properly earlier. It got me to the code in my comment above. Thanks very much.
Bannor
Solution: CREATE TRIGGER "test_tbl_trig" BEFORE UPDATE OR DELETE ON "public"."test_tbl" FOR EACH ROW EXECUTE PROCEDURE "public"."test_tbl_trig_func"(); CREATE OR REPLACE FUNCTION "public"."test_tbl_trig_func" ( ) RETURNS trigger AS $body$ BEGIN IF not (old.pfr_author = "current_user"() OR "current_user"() = 'postgres') THEN RAISE EXCEPTION 'You do not have permission to modify this record.'; RETURN NULL; ELSE IF tg_op = 'DELETE' THEN RETURN old; ELSE RETURN new; END IF; END IF; END; $body$ LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER COST 100;
Bannor
A: 

the trigger function have some problem,resulting recursive loop update.You should do like this:

CREATE OR REPLACE FUNCTION "public"."test_tbl_trig_func" () RETURNS trigger AS $body$ 
BEGIN 
IF not (old.pfr_author = "current_user"() OR "current_user"() = 'postgres') THEN 
    NULL;  
END IF; 
RETURN new;
END; 
$body$ LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER COST 100; 

I have a test like this,it does well;

UPDATE test_tbl SET testdate = CURRENT_DATE WHERE test_tbl."index" = 2; 
tinychen
Nice! You completely switched the approach. Can't believe how obvious it is now. :-) Thanks!
Bannor
The e.g. code didn't appear to work when I tested it with a different username, but together both answers got me to the code below:
Bannor
CREATE TRIGGER "test_tbl_trig" BEFORE UPDATE OR DELETE ON "public"."test_tbl" FOR EACH ROW EXECUTE PROCEDURE "public"."test_tbl_trig_func"();
Bannor
CREATE OR REPLACE FUNCTION "public"."test_tbl_trig_func" ()RETURNS trigger AS$body$BEGIN IF not (old.pfr_author = "current_user"() OR "current_user"() = 'postgres') THEN RAISE EXCEPTION 'You do not have permission to modify this record.'; RETURN NULL;ELSE IF tg_op = 'DELETE' THEN RETURN old; ELSE RETURN new; END IF;END IF;END;$body$LANGUAGE 'plpgsql'VOLATILECALLED ON NULL INPUTSECURITY INVOKERCOST 100;
Bannor