Thread: Cascading deletes with rules in 7.0.3: why doesn't this work?

Cascading deletes with rules in 7.0.3: why doesn't this work?

From
Jeremy Radlow
Date:
In the following example, I'm trying to log all deletions from any of three
tables.  The only information I'm interested in recording is the index from
table A which the deleted row referenced.

When I try to delete a row from A or B:

testdb=> delete from b;
ERROR:  ExecAppend: Fail to add null value in not null attribute ax
testdb=> delete from c;
DELETE 1
testdb=> delete from b;
DELETE 1

What's really odd is that if I drop the *_delete rules, and try 'delete
from a' in the same psql session, I get that ExecAppend error.  But
'delete from a' works in a new session:

testdb#1=> delete from a;
ERROR:  ExecAppend: Fail to add null value in not null attribute ax
testdb#1=> drop rule a_delete;
DROP
testdb#1=> drop rule b_delete;
DROP
testdb#1=> drop rule c_delete;
DROP
testdb#1=>
[1]+  Stopped       psql -d testdb -U tester
postgres# psql -d testdb -U tester
testdb#2=> delete from a;
DELETE 1
testdb#2=> \q
postgres# fg
testdb#1=> insert into a values (1);
INSERT 1961282 1
testdb#1=> insert into b values (1,1);
INSERT 1961283 1
testdb#1=> insert into c values (1,1);
INSERT 1961284 1
testdb#1=> delete from a;
ERROR:  ExecAppend: Fail to add null value in not null attribute ax
testdb#1=>


Does anyone know what the problem is?

Thanks,

Jeremy

-- cut here --

create table a (
     ax serial,
     primary key (ax)
);

create table b (
     bx serial,
     ax int references a on delete cascade,
     primary key (bx)
);

create table c (
     cx serial,
     bx int references b on delete cascade,
     primary key (cx)
);

create table delete_log (
    ax int not null default '0',
    delete_timestamp timestamp default current_timestamp
);

create rule a_delete as
on delete to a do
    insert into delete_log (ax) values (old.ax);

create rule b_delete as
on delete to b do
    insert into delete_log (ax) values (old.ax);

create rule c_delete as
on delete to c do
    insert into delete_log (ax)
           values ((select ax from b where b.bx = old.bx));


insert into a values (1);
insert into b values (1,1);
insert into c values (1,1);


Re: Cascading deletes with rules in 7.0.3: why doesn't this work?

From
Tom Lane
Date:
Jeremy Radlow <jtr@sourcerers.com> writes:
> In the following example, I'm trying to log all deletions from any of three
> tables.  The only information I'm interested in recording is the index from
> table A which the deleted row referenced.

This seems to be a bug.  I haven't finished tracking it down yet, but
when you delete from A or B, the ON DELETE CASCADE trigger is going to
try to delete from B and/or C as well --- so the trigger will end up
invoking the ON DELETE rules for those tables and hence trying to insert
into delete_log.  This chain of events does indeed happen as expected,
but it seems that when we get down to the INSERT, the value being passed
for old.ax has become NULL.  Something rotten in parameter passing,
apparently.  (Deletion from C works, because that does not trigger any
cascaded deletes.)

Thanks for the report!  I'll try to fix it for 7.1...

            regards, tom lane

Re: Cascading deletes with rules in 7.0.3: why doesn't this work?

From
Tom Lane
Date:
Tom Lane <tgl@sss.pgh.pa.us> writes:
> Jeremy Radlow <jtr@sourcerers.com> writes:
>> In the following example, I'm trying to log all deletions from any of three
>> tables.  The only information I'm interested in recording is the index from
>> table A which the deleted row referenced.

> This seems to be a bug.

No, there's no bug.  The problem is the weird way you defined the
c_delete rule:

create rule c_delete as
on delete to c do
    insert into delete_log (ax)
           values ((select ax from b where b.bx = old.bx));

This depends on the assumption that there will be an entry in b that
matches the bx value of the c row being deleted.  Unfortunately, when
this delete is fired from an ON CASCADE DELETE from table b, the
relevant row of b is already gone (as far as this transaction is
concerned, anyway).  So the subselect yields no rows and you end up
trying to insert a null into delete_log.

Seems to me you want to record the A reference value directly in rows
of C, so that C's delete rule can look the same as B's.

            regards, tom lane

Re: Cascading deletes with rules in 7.0.3: why doesn't this work?

From
Jeremy Radlow
Date:
Thanks for the insight.  The problem is that logging ax is basically a
convenience for the end user.  (A is a user table)  I must reference B
from C.  If I also add an A reference to C, I want the A reference to
be automatically maintained at the database level.  And this is where
things start getting really convoluted...

I create a real C (c_real) and a view (c):

create table c_real (
     cx serial,
     bx int references b on delete cascade,
     ax int references a,
     primary key (cx)
);

create view c as select * from c_real;

Then I add rules to make c masquerade as a real table which supports
inserts/deletes/updates.  And the insert rule changes ax:

create rule c_insert as
    on insert to c do instead
    insert into c_real values (new.cx, new.bx,
                         (select ax from b where b.bx = new.bx) );

Then there would be a trigger in B to change C whenever B's reference
to A changes.

I hope there's a simpler solution!

-Jeremy

At 11:52 AM 4/2/01 -0400, Tom Lane wrote:
>there's no bug.  The problem is the weird way you defined the
>c_delete rule:
>
>create rule c_delete as
>on delete to c do
>     insert into delete_log (ax)
>            values ((select ax from b where b.bx = old.bx));
>
>This depends on the assumption that there will be an entry in b that
>matches the bx value of the c row being deleted.  Unfortunately, when
>this delete is fired from an ON CASCADE DELETE from table b, the
>relevant row of b is already gone (as far as this transaction is
>concerned, anyway).  So the subselect yields no rows and you end up
>trying to insert a null into delete_log.
>
>Seems to me you want to record the A reference value directly in rows
>of C, so that C's delete rule can look the same as B's.