Thread: AfterTriggerSaveEvent() called outside of query

AfterTriggerSaveEvent() called outside of query

From
Vick Khera
Date:
I'm running a script I have for cleaning out some old data.  It
iterates over a handful of tables and deletes any data referencing a
row in an "owners" table.  Basically, it enforces the equivalent of
the cascade delete of the data without actually deleting the master
row.

The code looks basically like this, for each owner_id ($oid) targeted
for the purge:

BEGIN;
SET LOCAL synchronous_commit TO OFF;
SET CONSTRAINTS ALL DEFERRED;

foreach table (owner_log, ... user_list, invoices, ... )
 DELETE FROM $table WHERE owner_id=$oid

UPDATE owner SET status='terminated' WHERE owner_id=$oid
INSERT INTO admin_log (.... record of account being purged ... );
COMMIT;

This worked just dandy without the two SET commands above I added
yesterday to try to speed things up.  (Yes, the constraints are marked
as deferrable...) What happens is now I get the following error:


 ERROR:  AfterTriggerSaveEvent() called outside of query
 CONTEXT:  SQL statement "DELETE FROM ONLY "public"."user_event_log"
WHERE $1 OPERATOR(pg_catalog.=) "user_id"" at ./purgeoldownerinfo line
77.


The context is showing a row being deleted via FK reference to the
user_list table user_id primary key field, which had an explicit
delete done by the loop.

I'm running this via Perl DBD::Pg connecting to a postgres 8.3.11
server on FreeBSD 8.1.  The tables in this database are replicated
using slony1.

Re: AfterTriggerSaveEvent() called outside of query

From
Tom Lane
Date:
Vick Khera <vivek@khera.org> writes:
> The code looks basically like this, for each owner_id ($oid) targeted
> for the purge:

> BEGIN;
> SET LOCAL synchronous_commit TO OFF;
> SET CONSTRAINTS ALL DEFERRED;

> foreach table (owner_log, ... user_list, invoices, ... )
>  DELETE FROM $table WHERE owner_id=$oid

> UPDATE owner SET status='terminated' WHERE owner_id=$oid
> INSERT INTO admin_log (.... record of account being purged ... );
> COMMIT;

> This worked just dandy without the two SET commands above I added
> yesterday to try to speed things up.  (Yes, the constraints are marked
> as deferrable...) What happens is now I get the following error:

>  ERROR:  AfterTriggerSaveEvent() called outside of query

The code comment associated with that error message says:

     * Check state.  We use normal tests not Asserts because it is possible
     * to reach here in the wrong state given misconfigured RI triggers,
     * in particular deferring a cascade action trigger.

The system will not normally allow cascade actions to be deferred
... did you manually munge the pg_trigger entries?  If you managed
to provoke this purely through DDL commands, that would be a bug,
and I'd like to see how you did it.

            regards, tom lane

Re: AfterTriggerSaveEvent() called outside of query

From
Vick Khera
Date:
On Tue, Nov 16, 2010 at 12:40 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> The system will not normally allow cascade actions to be deferred
> ... did you manually munge the pg_trigger entries?  If you managed
> to provoke this purely through DDL commands, that would be a bug,
> and I'd like to see how you did it.
>

Based on advice gleaned from here, earlier this year we did the
following to make the constraints deferrable.  There were "warrantee
breaking" warnings with that advice, though :(  I guess I broke it.


UPDATE pg_trigger SET tgdeferrable='t' WHERE oid IN (SELECT objid FROM
pg_depend WHERE refobjid IN (SELECT oid FROM pg_constraint WHERE
condeferrable='f' AND contype='f' AND connamespace=2200));
UPDATE pg_constraint SET condeferrable='t' WHERE condeferrable='f' AND
contype='f' AND connamespace=2200;

Is there a way to better limit that to avoid the FK constraints?

When we do a pg_dump for the schema, the FK constraints do show
DEFERRABLE like this:

ALTER TABLE ONLY user_event_log
    ADD CONSTRAINT user_event_log_user_id_fkey FOREIGN KEY (user_id)
REFERENCES user_list(user_id) ON DELETE CASCADE DEFERRABLE;

The above also is how it looks when I load my current schema into a Pg
9.0 instance and run pg_dump to get it back.  I'm guessing that the
deferrable here only applies to the existence test, not the cascade,
and when I hacked the pg_trigger entries it made the cascade bits also
deferrable.

It should all be fixed up when we do the migration to 9.0 since I will
load the schema freshly from the pg_dump then have slony copy the
data.

Thanks!

Re: AfterTriggerSaveEvent() called outside of query

From
Tom Lane
Date:
Vick Khera <vivek@khera.org> writes:
> On Tue, Nov 16, 2010 at 12:40 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> The system will not normally allow cascade actions to be deferred
>> ... did you manually munge the pg_trigger entries? �If you managed
>> to provoke this purely through DDL commands, that would be a bug,
>> and I'd like to see how you did it.

> Based on advice gleaned from here, earlier this year we did the
> following to make the constraints deferrable.  There were "warrantee
> breaking" warnings with that advice, though :(  I guess I broke it.

> UPDATE pg_trigger SET tgdeferrable='t' WHERE oid IN (SELECT objid FROM
> pg_depend WHERE refobjid IN (SELECT oid FROM pg_constraint WHERE
> condeferrable='f' AND contype='f' AND connamespace=2200));
> UPDATE pg_constraint SET condeferrable='t' WHERE condeferrable='f' AND
> contype='f' AND connamespace=2200;

Yeah, that was overambitious.  You should have set just the check
triggers, not the cascade triggers, to be deferrable.  Try making a
deferrable constraint the regular way and have a look at the pg_trigger
entries it creates.  For example,

create table m (f1 int primary key);
create table s (f2 int references m ON DELETE CASCADE DEFERRABLE);
select tgfoid::regproc, tgrelid::regclass, * from pg_trigger order by oid desc limit 4;

On HEAD I get this:

         tgfoid         | tgrelid | tgrelid |           tgname           | tgfoid | tgtype | tgenabled | tgisinternal |
tgconstrrelid| tgconstrindid | tgconstraint | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual  

------------------------+---------+---------+----------------------------+--------+--------+-----------+--------------+---------------+---------------+--------------+--------------+----------------+---------+--------+--------+--------
 "RI_FKey_noaction_upd" | m       |   41310 | RI_ConstraintTrigger_41322 |   1655 |     17 | O         | t            |
       41315 |         41313 |        41318 | t            | f              |       0 |        | \x     |  
 "RI_FKey_cascade_del"  | m       |   41310 | RI_ConstraintTrigger_41321 |   1646 |      9 | O         | t            |
       41315 |         41313 |        41318 | f            | f              |       0 |        | \x     |  
 "RI_FKey_check_upd"    | s       |   41315 | RI_ConstraintTrigger_41320 |   1645 |     17 | O         | t            |
       41310 |         41313 |        41318 | t            | f              |       0 |        | \x     |  
 "RI_FKey_check_ins"    | s       |   41315 | RI_ConstraintTrigger_41319 |   1644 |      5 | O         | t            |
       41310 |         41313 |        41318 | t            | f              |       0 |        | \x     |  
(4 rows)

Notice the RI_FKey_cascade_del trigger is not deferrable.

> Is there a way to better limit that to avoid the FK constraints?

I think the code in the backend that does this just has a hard-wired
list of which trigger function OIDs to exclude from deferrability.

            regards, tom lane