My code example ended up quite big—so I'll show it to you all only if you ask. But it's easy to describe. My script does this: 1. It creates three non-superuser roles: "data", "code", and "client". 2. It creates a text-book masters-and-details table pair with owner "data". Each table has the obvious PK column. The "details" table has the obvious FK column. And each table has a payload column "v". The FK constraint is defined thus: constraint details_fk foreign key(mk) references masters(mk) match full on delete cascade on update restrict initially deferred 3. It creates a DELETE trigger at each timing point on each table. That's eight in all: [before|after] * [row|statement] * [masters|details]. The trigger functions have the same names as the triggers. And each name pair reflects the three degrees of freedom—for example "before_statement_masters". Each trigger function simply does a "raise info" to report its name, the return value from "current_role", (and, in the "row" case, the value of "old.v"). And then it does "return null". The trigger functions are owned by "data" and are explicitly marked "security invoker". (The results are the same without this marking—as expected.) (In my real use case, the trigger functions are marked "security definer". But I did this test in order to understand the rules.) "data" grants appropriate privileges to "code" to let its functions succeed. 4. It creates three "security definer" procedures with owner "code" "cr_m_and_ds()" inserts specified "details" rows and then their specified "masters" row. (That's why the FK constraint is "initially deferred". The use-case from which this test is derived needs this.) "del_m()" deletes a specified "masters" row—which implies the cascade-delete of its details. "del_ds()" deletes all the "details" rows for a specified master. "code" grants "execute" on these to "client". ("client " owns no objects.) 5. "client" invokes "code.cr_m_and_ds()". It's called to insert a single "masters" row with "masters.v" set to 'Mary' and a single "details" row with "details.v" set to 'shampoo'. 6. "client" invokes "code.del_m()" on 'Mary'. Here's what I see: before_statement_masters: [code] before_row_masters: [code] Mary before_statement_details: [data] before_row_details: [data] shampoo after_row_masters: [code] Mary after_statement_masters: [code] after_row_details: [code] shampoo after_statement_details: [code] (I stripped the noisy "psql:0.sql:32: INFO:" preamble for each output line by hand.) I was surprised that the value from "current_role" is *not* the table owner, "data", in all cases. (This is how triggers behave in Oracle database.) Rather, it's mainly (but not always) "code". I could be persuaded that, in the cascade-delete case, the invoking role is the owner of the "masters" table rather than the role, "code" that performs the "delete" from "masters"—but that would maybe be a stretch. Anyway, if this is the intention, why is it like this only for the "before" timing points for the triggers on "details"? 7. Starting with the same 'Mary'-'shampoo' pair, client invokes "code.del_ds()" on 'Mary' With the same set-up, and using this instead of "del_m()", this is the result: before_statement_details: [code] before_row_details: [code] shampoo after_row_details: [code] shampoo after_statement_details: [code] Here the value for "current_role" from each trigger is the same. These results are in line with the common case in the first test. I read the section "Triggers on Data Changes" (https://www.postgresql.org/docs/current/plpgsql-trigger.html#PLPGSQL-DML-TRIGGER). But there's no hit on the page for any of "security", "invoker", or "definer". And I couldn't find wording without these terms that addresses what I describe here. |