problems with foreign keys on partitioned tables

From: Amit Langote <Langote_Amit_f8(at)lab(dot)ntt(dot)co(dot)jp>
To: Pg Hackers <pgsql-hackers(at)postgresql(dot)org>
Cc: Alvaro Herrera <alvherre(at)2ndquadrant(dot)com>
Subject: problems with foreign keys on partitioned tables
Date: 2019-01-09 10:21:38
Message-ID: f2b8ead5-4131-d5a8-8016-2ea0a31250af@lab.ntt.co.jp
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

I noticed a couple of problems with foreign keys on partitioned tables.

1. Foreign keys of partitions stop working correctly after being detached
from the parent table

create table pk (a int primary key);
create table p (a int) partition by list (a);
create table p1 partition of p for values in (1) partition by list (a);
create table p11 partition of p1 for values in (1);
alter table p add foreign key (a) references pk (a);

-- these things work correctly
insert into p values (1);
ERROR: insert or update on table "p11" violates foreign key constraint
"p_a_fkey"
DETAIL: Key (a)=(1) is not present in table "pk".
insert into pk values (1);
insert into p values (1);
delete from pk where a = 1;
ERROR: update or delete on table "pk" violates foreign key constraint
"p_a_fkey" on table "p"
DETAIL: Key (a)=(1) is still referenced from table "p".

-- detach p1, which preserves the foreign key key
alter table p detach partition p1;
create table p12 partition of p1 for values in (2);

-- this part of the foreign key on p1 still works
insert into p1 values (2);
ERROR: insert or update on table "p12" violates foreign key constraint
"p_a_fkey"
DETAIL: Key (a)=(2) is not present in table "pk".

-- but this seems wrong
delete from pk where a = 1;
DELETE 1

-- because
select * from p1;
a
───
1
(1 row)

This happens because the action triggers defined on the PK relation (pk)
refers to p as the referencing relation. On detaching p1 from p, p1's
data is no longer accessible to that trigger. To fix this problem, we
need create action triggers on PK relation that refer to p1 when it's
detached (unless such triggers already exist which might be true in some
cases). Attached patch 0001 shows this approach.

2. Foreign keys of a partition cannot be dropped in some cases after
detaching it from the parent.

create table p (a int references pk) partition by list (a);
create table p1 partition of p for values in (1) partition by list (a);
create table p11 partition of p1 for values in (1);
alter table p detach partition p1;

-- p1's foreign key is no longer inherited, so should be able to drop it
alter table p1 drop constraint p_a_fkey ;
ERROR: constraint "p_a_fkey" of relation "p11" does not exist

This happens because by the time ATExecDropConstraint tries to recursively
drop the p11's inherited foreign key constraint (which is what normally
happens for inherited constraints), the latter has already been dropped by
dependency management. I think the foreign key inheritance related code
doesn't need to add dependencies for something that inheritance recursion
can take of and I can't think of any other reason to have such
dependencies around. I thought maybe they're needed for pg_dump to work
correctly, but apparently not so.

Interestingly, the above problem doesn't occur if the constraint is added
to partitions by inheritance recursion.

create table p (a int) partition by list (a);
create table p1 partition of p for values in (1) partition by list (a);
create table p11 partition of p1 for values in (1);
alter table p add foreign key (a) references pk (a);
alter table p detach partition p1;
alter table p1 drop constraint p_a_fkey ;
ALTER TABLE

Looking into it, that happens to work *accidentally*.

ATExecDropInherit() doesn't try to recurse, which prevents the error in
this case, because it finds that the constraint on p1 is marked NO INHERIT
(non-inheritable), which is incorrect. The value of p1's constraint's
connoinherit (in fact, other inheritance related properties too) is
incorrect, because ATAddForeignKeyConstraint doesn't bother to set them
correctly. This is what the inheritance properties of various copies of
'p_a_fkey' looks like in the catalog in this case:

-- case 1: foreign key added to partitions recursively
create table p (a int) partition by list (a);
create table p1 partition of p for values in (1) partition by list (a);
create table p11 partition of p1 for values in (1);
alter table p add foreign key (a) references pk (a);
select conname, conrelid::regclass, conislocal, coninhcount, connoinherit
from pg_constraint where conname like 'p%fkey%';
conname │ conrelid │ conislocal │ coninhcount │ connoinherit
──────────┼──────────┼────────────┼─────────────┼──────────────
p_a_fkey │ p │ t │ 0 │ t
p_a_fkey │ p1 │ t │ 0 │ t
p_a_fkey │ p11 │ t │ 0 │ t
(3 rows)

In this case, after detaching p1 from p, p1's foreign key's coninhcount
turns to -1, which is not good.

alter table p detach partition p1;
select conname, conrelid::regclass, conislocal, coninhcount, connoinherit
from pg_constraint where conname like 'p%fkey%';
conname │ conrelid │ conislocal │ coninhcount │ connoinherit
──────────┼──────────┼────────────┼─────────────┼──────────────
p_a_fkey │ p │ t │ 0 │ t
p_a_fkey │ p11 │ t │ 0 │ t
p_a_fkey │ p1 │ t │ -1 │ t
(3 rows)

-- case 2: foreign keys cloned to partitions after adding partitions
create table p (a int references pk) partition by list (a);
create table p1 partition of p for values in (1) partition by list (a);
create table p11 partition of p1 for values in (1);
select conname, conrelid::regclass, conislocal, coninhcount, connoinherit
from pg_constraint where conname like 'p%fkey%';
conname │ conrelid │ conislocal │ coninhcount │ connoinherit
──────────┼──────────┼────────────┼─────────────┼──────────────
p_a_fkey │ p │ t │ 0 │ t
p_a_fkey │ p1 │ f │ 1 │ f
p_a_fkey │ p11 │ f │ 1 │ f
(3 rows)

Anyway, I propose we fix this by first getting rid of dependencies for
foreign key constraints and instead rely on inheritance recursion for
dropping the inherited constraints. Before we can do that, we'll need to
consistently set the inheritance properties of foreign key constraints
correctly, that is, teach ATAddForeignKeyConstraint what
clone_fk_constraints already does correctly. See the attached patch 0002
for that.

I'm also attaching versions of 0001 and 0002 that can be applied to PG 11.

Thanks,
Amit

Attachment Content-Type Size
PG11-v1-0001-Ensure-PK-side-action-triggers-for-partitions-aft.patch text/plain 5.9 KB
PG11-v1-0002-Do-not-track-foreign-key-inheritance-by-dependenc.patch text/plain 8.9 KB
v1-0001-Ensure-PK-side-action-triggers-for-partitions-aft.patch text/plain 5.9 KB
v1-0002-Do-not-track-foreign-key-inheritance-by-dependenc.patch text/plain 9.0 KB

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Iwata, Aya 2019-01-09 10:25:28 RE: libpq compression
Previous Message Mi Tar 2019-01-09 09:05:20 Re: port of INSTALL file generation to XSLT