views:

293

answers:

5

I would like to force user to specify origin of update to some table (sometbl), eg. to specify 'local' or 'remote' (for col2) - but checking of that requirement should occur at DB level when UPDATE statement is executed so:

UPDATE sometbl SET col1 = 'abc';

should throw error (exception), but:

UPDATE sometbl SET col1 = 'abc', col2 = 'remote';

...will succeed.

I tried to create BEFORE update trigger for that table, but I was unable to check if NEW.col2 was explictly set.

I used condition

IF NEW.col2 IS NULL THEN 
   RAISE EXCEPTION 'you must specify source of this update (local/remote)'
END IF;

but every time, when col2 was not specified in update (UPDATE sometbl SET col1 = 'abc')

I got current value of that field in NEW.col2 pseudo-var, instead of supposed NULL.

Is there any workaround to prevent UPDATING row when specified field is not present in UPDATE stmt?

A: 

You could create a second table that contains your local and remote entries with an ID value, then simply use a not-null foreign key to that table in the first table.

Jon Seigel
That solution won't work in my case, because in first table a row(s) with valid foreign key will exists prior to UPDATING, so update will succeed even when no foreign key field value was specified.
Dominik Sadowski
A: 

You could create a couple of stored procedures, a la

create or replace function update_remote(text) returns void
    as 'update sometbl SET col1 = $1, col2 = ''remote'''
    language SQL
    volatile
    strict;
Jonathan Feinberg
That partially solves the problem I'm facing with - to force someone who will update the table to explictly set origin of that update. I mean partially because that someone have to be told to use that procedure(s) and not to use direct UPDATE statement. Pooking with permissions can't make it - if db user have EXECUTE on that functions, in order to update table through that function he must also posses UPDATE priviledge (which in turn allows for executing UPDATE without required field set).
Dominik Sadowski
+1  A: 

How about a couple of triggers? One runs before the update, and sets the column to null. One runs after the update and pukes (returns NULL) if the column is still null. (If a trigger returns NULL, the update fails.)

Jonathan Feinberg
A clever solution.
Kev
I'd call it "grasping at straws". :)
Jonathan Feinberg
It was promising solution for me at first look...but, did you actually try that on real example? Still not working for me. First of all AFTER trigger may return NULL and UPDATE will not fail, it'll fail only when you explictly throw exception in AFTER trigger function's body. You suggested to set column to null in before trigger - but how? using separate UPDATE stmt in that trigger? That leads to recursive invocation of the same trigger, and even If this could be avoided using some conditional checking - it would not be possible to find out if 'col2' was explictly set in updt stmt as I require
Dominik Sadowski
+1  A: 

I'd use a security definer function, owned by the only user besides admin and/or table owner, that has update privilege on sometbl.

Something like this:

create table sometbl (
  id serial primary key,
  col1 text,
  col2 text not null,
    check (col2 in ('local','remote'))
);
create role sometbl_updater;
grant update on sometbl to sometbl_updater;

create function update_sometbl(integer, text, text)
returns void as
$$
  update sometbl set col1=$2, col2=$3 where id=$1;
$$ security definer volatile language sql;
alter function update_sometbl(integer, text, text)
  owner to sometbl_updater;

But be careful about security definer functions security.

Tometzky
A: 

OK, having read the docs and tried it out, I can report that a BEFORE trigger is the way to go. A trigger provides the names NEW and OLD bound to the new and former tuples. Returning NULL from a BEFORE trigger prevents the update. Hence:

CREATE OR REPLACE FUNCTION prevent_not_changing_col2()
  RETURNS trigger AS $$
begin
  if NEW.col2 = OLD.col2 then return NULL; end if;
  return NEW;
end ;
$$ LANGUAGE plpgsql;

CREATE TRIGGER col2_check
  BEFORE UPDATE
  ON sometbl
  FOR EACH ROW
  EXECUTE PROCEDURE prevent_not_changing_col2();
Jonathan Feinberg
Ok, but that solution have one drawback - it will prevent also UPDATE where col2 is explictly set to the same value as in updated row, suppose there is row in sometbl(id,col1,col2): 1,'abc','local' and we try "UPDATE sometbl SET col1 = 'def', col2 = 'local' WHERE id = 1", then that trigger will also prevent former update stmt which I would like to force using to all developers (they must provide col2 in updt stmts, no matter if it value changed, or not). am I right ?
Dominik Sadowski