I’d like to create trigger function, without specifying by name the other columns that can’t be updated.
Example:
Create table test(id integer, name text, amount numeric);
I’d like to prevent updates of any columns other than amount
, but to avoid specifying by name columns id
and name
like this
IF NEW.id <> OLD.id or NEW.name <> OLD.name THEN RAISE EXCEPTION 'UPDATE DISALLOWED'; END IF;
I would like to apply this trigger to multiple tables, that’s why I don’t want to specify the other columns.
Maybe it’s somehow possible using row level security? But I need to prevent also postgres role from the updates. I haven’t found how to do it.
@Edit
After hints I think I’m close to the solution, but I’m getting syntax error while trying to execute generated IF statement.
https://dbfiddle.uk/vp85wHgc
3
Answers
I figured out following solution:
You can do something like this, but identifying what the changed column is probably does need dynamic SQL.
Use @RichardHuxton’s answer. But if you want dynamic SQL, you can get a list of columns as a string list. Then split them up. The table name seems like a predefined variable TG_TABLE_NAME for a trigger function.
To get the column list, this is the typical way. However, the ORDER BY here will not give you the order of the columns correctly unless you list the columns explicitly. 99/100 the ORDER BY will be correct, but do not trust it.
If you want the order YOU built the table with, rather than the order in which * displays the columns in Postgres, you need to look at this. attnum is the order index. attname is the column name.
Reference:
https://www.postgresql.org/docs/current/plpgsql-trigger.html
Dynamic SQL Example:
Anything in the string after EXECUTE is what you would normally have as valid SQL code. So the IF statement goes inside there. If the IF statement contains values assigned to variables, then concatenate the variables to the string as I’ve done with TG_TABLE_NAME.
So given your question code, we’d have this.
Now take this a step further and find the ID_COLUMN and NAME_COLUMN variables using the query above to find the column names. If id is ALWAYS the first column and name is ALWAYS the second column, assign the list of column names (in proper order) returned to a variable COLUMN_LIST using the result set to list function (this is your homework). Then find the function(s) in Postgres to grab piece 1 and assign to ID_COLUMN variable and piece 2 to NAME_COLUMN.
Error in your code for the IF statement was not having a space before THEN. And I pushed everything to one line.