Обсуждение: Combination of Triggers and self-FKs produces inconsistent data

Поиск
Список
Период
Сортировка

Combination of Triggers and self-FKs produces inconsistent data

От
Josh Berkus
Дата:
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)

Re: Combination of Triggers and self-FKs produces inconsistent data

От
Tom Lane
Дата:
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

Re: Combination of Triggers and self-FKs produces inconsistent data

От
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

Re: Combination of Triggers and self-FKs produces inconsistent data

От
Josh Berkus
Дата:
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

Re: Combination of Triggers and self-FKs produces inconsistent data

От
Gregory Stark
Дата:
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!

Re: Combination of Triggers and self-FKs produces inconsistent data

От
Josh Berkus
Дата:
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