Обсуждение: BUG #17945: Different order of definition of a constraint causes constraint violation

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

BUG #17945: Different order of definition of a constraint causes constraint violation

От
PG Bug reporting form
Дата:
The following bug has been logged on the website:

Bug reference:      17945
Logged by:          Yann Salaün
Email address:      yannsalaun1@gmail.com
PostgreSQL version: 15.3
Operating system:   Linux
Description:

This is a bug report with a reproducible case where different orders of
definition of a SQL constraint causes different behavior.

To be more specific, if the constraint is defined inside the table
definition as in
```sql
CREATE TABLE node (
    id integer PRIMARY KEY,
    parent_id integer REFERENCES node(id) ON DELETE CASCADE
);
```
the behavior is not the same as when the constraint is defined after all
tables are defined (like in the output of pg_dump) as in
```sql
CREATE TABLE node (
    id integer PRIMARY KEY,
    parent_id integer
);
-- define all other tables...
ALTER TABLE node
    ADD CONSTRAINT nodes_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES
node(id) ON DELETE CASCADE;
```

The reproductible case comes from an application with tables modelling
filesystem trees. When a parent node is deleted, children are deleted in
cascade via foreign key constraints.

Here is the full psql script to reproduce the problem:
```sql
-- Schema definition
CREATE TABLE node (
    id integer PRIMARY KEY,
    parent_id integer
);
CREATE TABLE dir (
    id integer PRIMARY KEY REFERENCES node(id) ON DELETE CASCADE
);
CREATE TABLE file (
    id integer PRIMARY KEY REFERENCES node(id) ON DELETE CASCADE
);
CREATE TABLE t (
    main_dir_id integer REFERENCES dir(id) ON DELETE SET NULL,
    main_file_id integer REFERENCES file(id) ON DELETE SET NULL,
    other_file_id integer REFERENCES file(id) ON DELETE SET NULL
);

-- Constraint definition. If we inline this constraint in the table
definition, the SQL error below disappears.
ALTER TABLE node
    ADD CONSTRAINT nodes_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES
node(id) ON DELETE CASCADE;

-- Data insertion
-- main_dir
INSERT INTO node (id) VALUES (1);
INSERT INTO dir (id) VALUES (1);

-- main_file in main_dir
INSERT INTO node (id, parent_id) VALUES (2, 1);
INSERT INTO file (id) VALUES (2);

-- other_file
INSERT INTO node (id) VALUES (3);
INSERT INTO file (id) VALUES (3);

INSERT INTO t (main_dir_id, main_file_id, other_file_id) VALUES (1, 2, 3);

-- Data deletion in a transaction.
BEGIN;

-- First, delete other_file (this sets t.other_file_id to NULL via ON DELETE
SET NULL)
DELETE FROM node WHERE id = 3;

-- Then delete main_dir (this sets t.main_dir_id and t.main_file_id to NULL
via ON DELETE SET NULL)
DELETE FROM node WHERE id = 1;

-- The second DELETE statement returns the following error.

-- ERROR:  23503: insert or update on table "t" violates foreign key
constraint "t_main_file_id_fkey"
-- DETAIL:  Key (main_file_id)=(2) is not present in table "file".
-- SCHEMA NAME:  public
-- TABLE NAME:  t
-- CONSTRAINT NAME:  t_main_file_id_fkey
-- LOCATION:  ri_ReportViolation, ri_triggers.c:2528

ROLLBACK;
```

I believe this psql script is sufficient to reproduce the bug. Let me know
if that's not the case, I would be happy to provide more details.

Thanks for your answer.

Yann


Re: BUG #17945: Different order of definition of a constraint causes constraint violation

От
Jeff Davis
Дата:
On Thu, 2023-05-25 at 14:39 +0000, PG Bug reporting form wrote:
> This is a bug report with a reproducible case where different orders
> of
> definition of a SQL constraint causes different behavior.

Thank you for the report and repro! Yes, I believe this is a bug.

> - Then delete main_dir (this sets t.main_dir_id and t.main_file_id to
> NULL
> via ON DELETE SET NULL)
> DELETE FROM node WHERE id = 1;
>
> -- The second DELETE statement returns the following error.
>
> -- ERROR:  23503: insert or update on table "t" violates foreign key
> constraint "t_main_file_id_fkey"
> -- DETAIL:  Key (main_file_id)=(2) is not present in table "file".

Here's what's happening:

  DELETE FROM node WHERE id = 1;
  -- queues: DELETE FROM dir WHERE id = 1;
  -- queues: DELETE FROM file WHERE id = 1;
  -- queues: DELETE FROM node WHERE parent_id = 1;
  DELETE FROM dir WHERE id = 1;
  -- queues: UPDATE t SET main_dir_id = NULL WHERE main_dir_id = 1;
  DELETE FROM file WHERE id = 1;
  DELETE FROM node WHERE parent_id = 1;
  -- queues: DELETE FROM dir WHERE id = 2;
  -- queues: DELETE FROM file WHERE id = 2;
  -- queues: DELETE FROM node WHERE parent_id = 2;
  UPDATE t SET main_dir_id = NULL WHERE main_dir_id = 1;
  -- queues RI_FKey_check_upd for t.main_file_id
  DELETE FROM dir WHERE id = 2;
  DELETE FROM file WHERE id = 2;
  -- queues: UPDATE t SET main_file_id = NULL WHERE main_file_id = 2;
  -- queues: UPDATE t SET other_file_id = NULL WHERE other_file_id = 2;
  DELETE FROM node WHERE parent_id = 2;
  -- perform RI_FKey_check_upd for t.main_file_id, and fails

The problem is that it's performing the check on the tuple in "t"
before all of the pending actions have completed. In this particular
case, we may be able to supress that check entirely, but I don't think
that would be correct in general (e.g. overlapping FKs).

If the constraints are declared in a different order, they end up
queuing in a different order, and all of the SET NULL events fire
before the check, so there's no error.

One idea is to to have the check events re-queue themselves if there
are still actions remaining in the queue. That might violate the rules
about executing triggers in order by name, though.

Regards,
    Jeff Davis