Обсуждение: Combination of Triggers and self-FKs produces inconsistent data
Version: 8.3.5
Install: self-compile on 64-bit Ubuntu Linux
also reproduced by AndrewSN on another platform
Summary: self-referential FKs are not enforced properly in the
presence of BEFORE triggers
Test Case:
-- create two tables, one of which is the master table (reftable) the
other of which is a child which contains a tree structure (treetab):
create table reftable(
refid int primary key,
refname text
);
create table treetab (
id int primary key,
parent int,
refid int not null references reftable(refid) on delete cascade,
name text
);
-- now create a trigger function to maintain the integrity of the trees
in treetab by "pulling up"
-- each node to its parent if intermediate nodes get deleted
-- this trigger is inherently flawed and won't work with the FK below
create function treemaint () returns trigger as $t$
begin
update treetab set parent = OLD.parent
where parent = OLD.id;
return OLD;
end; $t$ language plpgsql;
create trigger treemaint_trg before delete on treetab
for each row execute procedure treemaint();
-- populate reftable
insert into reftable
select i, ( 'Ref' || i::TEXT ) from generate_series(1,100) as g(i);
-- populate treetab with 10 rows each pointing to reftable
insert into treetab (id, refid)
select i, (( i / 10::INT ) + 1 )
from generate_series (1,900) as g(i);
-- create trees in treetab. for this simple example each treeset is
just a chain with each child node
-- pointing to one higher node
update treetab set parent = ( id - 1 )
where id > (
select min(id) from treetab tt2
where tt2.refid = treetab.refid);
update treetab set "name" = ('tree' || parent::TEXT || '-' || id::TEXT);
-- now create a self-referential FK to enforce tree integrity. This
logically breaks the trigger
alter table treetab add constraint selfref foreign key (parent)
references treetab (id);
-- show tree for id 45
select * from treetab where refid = 45;
id | parent | refid | name
-----+--------+-------+-------------
440 | | 45 |
441 | 440 | 45 | tree440-441
442 | 441 | 45 | tree441-442
443 | 442 | 45 | tree442-443
444 | 443 | 45 | tree443-444
445 | 444 | 45 | tree444-445
446 | 445 | 45 | tree445-446
447 | 446 | 45 | tree446-447
448 | 447 | 45 | tree447-448
449 | 448 | 45 | tree448-449
-- now, we're going to delete the tree. This delete should fail with an
error because the
-- trigger will violate "selfref"
delete from reftable where refid = 45;
-- however, it doesn't fail. it reports success, and some but not all
rows from treetab
-- are deleted, leaving the database in an inconsistent state.
select * from treetab where refid = 45;
id | parent | refid | name
-----+--------+-------+-------------
441 | | 45 | tree440-441
443 | 441 | 45 | tree442-443
445 | 443 | 45 | tree444-445
447 | 445 | 45 | tree446-447
449 | 447 | 45 | tree448-449
-- this means we now have rows in the table which
-- violate the FK to reftable.
postgres=# select * from reftable where refid = 45;
refid | refname
-------+---------
(0 rows)
Josh Berkus <josh@agliodbs.com> writes:
> Summary: self-referential FKs are not enforced properly in the
> presence of BEFORE triggers
This isn't a bug. If you create triggers that prevent the RI actions
from being taken, it's your own problem.
regards, tom lane
Josh Berkus <josh@agliodbs.com> writes:
> Tom Lane wrote:
>> This isn't a bug. If you create triggers that prevent the RI actions
>> from being taken, it's your own problem.
> Huh? Since when was it OK by us to have data which violates a declared
> FK under *any* circumstances?
You can't have your cake and eat it too, Josh. If we make the RI
mechanism operate at a level underneath triggers, then we'll lose all
sorts of useful capability that people are depending on. A couple of
examples:
* the ability to log table changes caused by RI cascades
* the ability to maintain row update timestamps when the update is
caused by an RI cascade
> Where in our docs does it say that
> Foreign Keys are not enforced if the table has triggers on it?
It doesn't say that, because it isn't true. What is true is that if you
make a trigger that prevents updates from happening, it breaks RI
updates as well as directly-user-initiated updates. Either way, you're
going to need to fix the trigger.
regards, tom lane
Tom Lane wrote: > Josh Berkus <josh@agliodbs.com> writes: >> Summary: self-referential FKs are not enforced properly in the >> presence of BEFORE triggers > > This isn't a bug. If you create triggers that prevent the RI actions > from being taken, it's your own problem. Huh? Since when was it OK by us to have data which violates a declared FK under *any* circumstances? Where in our docs does it say that Foreign Keys are not enforced if the table has triggers on it? --Josh
Tom Lane <tgl@sss.pgh.pa.us> writes: > It doesn't say that, because it isn't true. What is true is that if you > make a trigger that prevents updates from happening, it breaks RI > updates as well as directly-user-initiated updates. Can we detect that this happened and throw an error? I suspect not, though, since we have no way to actually determine whether the user trigger didn't do something else equivalent. -- Gregory Stark EnterpriseDB http://www.enterprisedb.com Get trained by Bruce Momjian - ask me about EnterpriseDB's PostgreSQL training!
Tom, > You can't have your cake and eat it too, Josh. If we make the RI > mechanism operate at a level underneath triggers, then we'll lose all > sorts of useful capability that people are depending on. A couple of > examples: > > * the ability to log table changes caused by RI cascades > > * the ability to maintain row update timestamps when the update is > caused by an RI cascade Yeah, I can see that there isn't an obvious fix. However, at the end of the day it means that RI in Postgres can be accidentally broken by user action without removing or disabling the constraint. This isn't a comfortable thought; it sounds an awful lot like another OSS-DB. Or to put it another way, we don't allow triggers to break UNIQUE constraints or CHECK constraints. All of the other constraints operate at a level below triggers. Why are FKs different? > It doesn't say that, because it isn't true. What is true is that if you > make a trigger that prevents updates from happening, it breaks RI > updates as well as directly-user-initiated updates. Again, if we're going to retain this issue, then it needs to be in the documentation that RI isn't enforced on the results of triggers. Because, polling 5 people on IRC who each have more than 3 years of PostgreSQL experience ... and two of whom are code contributors ... this issue surprised *all* of them. > Either way, you're > going to need to fix the trigger. If you read to the end of the example, you'd see that I'm saying that the trigger should *fail*, with an error. Not work. Throughout the history of the project, no functionality which ends in a inconsistent data state has ever been acceptable which I can recall. When did we change our policy? --Josh Berkus