Thread: BUG #4417: Foreign keys do not work after altering table/column names

BUG #4417: Foreign keys do not work after altering table/column names

From
"Benjamin Bihler"
Date:
The following bug has been logged online:

Bug reference:      4417
Logged by:          Benjamin Bihler
Email address:      benjamin.bihler@gmx.de
PostgreSQL version: 8.3.1
Operating system:   SUSE Linux
Description:        Foreign keys do not work after altering table/column
names
Details:

We have a SQL-script to create tables and constraints, insert data into
these tables, alter the tables and again insert some data. There is one
"master" script file that includes subscripts with

\i Subscript.sql

These scripts do the following:

We create tables A and B with a foreign key stating that A.column references
B.column.

We rename table B into B_NEW and the column B.column into B_NEW.column_new.

Afterwards we want to update values in A.column. We get an error message
(translated from German):

Relation "public.B" does not exist.
CONTEXT: SQL statement "SELECT 1 FROM ONLY "public"."B" x WHERE "B.column"
OPERATOR(pg_catalog.=) $1 FOR SHARE OF x"

and

Updating or Deletion in table ... violates foreign key contraint.
DETAIL: There is still a reference to ... in table ...


These scripts work correctly with PostgreSQL 8.2.5.

Back to PostgreSQL 8.3.1: If we change the master script, so that it does
not include the subscripts to alter table B and to update A.column and we
call these subscripts separately after the master script has finished, they
WORK!

I mean:

psql -f Master_complete.sql DATABASE_NAME does not work!

psql -f Master_without_altering_B_and_updating_A.sql DATABASE_NAME
psql -f Altering_B_and_updating_A.sql DATABASE_NAME works!

Unfortunately it is not possible to give more details, since these scripts
are part of a commercial product.

Re: BUG #4417: Foreign keys do not work after altering table/column names

From
Heikki Linnakangas
Date:
Benjamin Bihler wrote:
> Unfortunately it is not possible to give more details, since these scripts
> are part of a commercial product.

It's unlikely that anyone can help you without more details. I'd suggest
that you try to reduce it to a smaller stand-alone, anonymizing it by
changing table and column names if necessary, and verify that you can
still reproduce the problem with the reduced script. And then post that
script to the mailing list.

Alternatively, there's many support companies out there, including the
company I work for, that can help you under a NDA.

--
   Heikki Linnakangas
   EnterpriseDB   http://www.enterprisedb.com

Re: BUG #4417: Foreign keys do not work after altering table/column names

From
Tom Lane
Date:
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
> Benjamin Bihler wrote:
>> Unfortunately it is not possible to give more details, since these scripts
>> are part of a commercial product.

> It's unlikely that anyone can help you without more details.

Although the OP could certainly have worked a bit harder at providing a
self-contained example, it's not hard to reproduce a problem:

regression=# create table atab(col int primary key);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "atab_pkey" for table "atab"
CREATE TABLE
regression=# create table btab(col int primary key);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "btab_pkey" for table "btab"
CREATE TABLE
regression=# alter table atab add column bref int references btab;
ALTER TABLE
regression=# insert into atab values(1,2);
ERROR:  insert or update on table "atab" violates foreign key constraint "atab_bref_fkey"
DETAIL:  Key (bref)=(2) is not present in table "btab".
regression=# insert into btab values(2);
INSERT 0 1
regression=# insert into atab values(1,2);
INSERT 0 1
regression=# alter table btab rename to b_new;
ALTER TABLE
regression=# alter table b_new rename col to col_new;
ALTER TABLE
regression=# update atab set bref = 3;

At this point 8.2 gives the expected error:

ERROR:  insert or update on table "atab" violates foreign key constraint "atab_bref_fkey"
DETAIL:  Key (bref)=(3) is not present in table "b_new".

but in CVS HEAD I get:

ERROR:  relation "public.btab" does not exist
LINE 1: SELECT 1 FROM ONLY "public"."btab" x WHERE "col" OPERATOR(pg...
                           ^
QUERY:  SELECT 1 FROM ONLY "public"."btab" x WHERE "col" OPERATOR(pg_catalog.=) $1 FOR SHARE OF x

What is evidently happening is that the plancache decides it needs to
redo the cached plan for this query, but it's regenerating it from
a static version of the query text.

8.2 is more or less accidentally failing to fail: it's got a cached
plan, which it is too stupid to try to replan, but that plan still
works because it's referring to B by OID and attnum.  There are huge
numbers of cases where 8.2 will fail miserably on alterations of the
tables involved in the FK, but simple renaming isn't one of them.
Whereas 8.3 handles all the other cases and falls down on renaming :-(

So I guess what we'd need to fix this is some sort of wart added to the
plancache mechanism that would allow the RI triggers to regenerate their
query text, not only update the derived plan, when a plan invalidation
event occurs.

My first thought was some kind of callback function to construct the
query text afresh, but that seems pretty baroque.  It'd be simpler
to just have an option to let plancache.c return a failure indication
when its plan is obsolete, whereupon ri_triggers.c discards that plan
and builds a fresh one.  [ pokes around... ]  Hm, the fly in that
ointment is that ri_triggers and plancache aren't communicating directly
but through SPI.  I'm really loath to change the SPI API for this;
is there another way?

            regards, tom lane

Re: BUG #4417: Foreign keys do not work after altering table/column names

From
Heikki Linnakangas
Date:
Tom Lane wrote:
> Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
>> Benjamin Bihler wrote:
>>> Unfortunately it is not possible to give more details, since these scripts
>>> are part of a commercial product.
>
>> It's unlikely that anyone can help you without more details.
>
> Although the OP could certainly have worked a bit harder at providing a
> self-contained example, it's not hard to reproduce a problem:
>
> ...

Oh, good.

> My first thought was some kind of callback function to construct the
> query text afresh, but that seems pretty baroque.  It'd be simpler
> to just have an option to let plancache.c return a failure indication
> when its plan is obsolete, whereupon ri_triggers.c discards that plan
> and builds a fresh one.  [ pokes around... ]  Hm, the fly in that
> ointment is that ri_triggers and plancache aren't communicating directly
> but through SPI.  I'm really loath to change the SPI API for this;
> is there another way?

ri_triggers.c could register a callback to invalidate entries in its
hash table with CacheRegisterRelcacheCallback() ?

Adding a new function, say SPI_is_plan_valid(plan), doesn't seem too bad
to me either.

--
   Heikki Linnakangas
   EnterpriseDB   http://www.enterprisedb.com

Re: BUG #4417: Foreign keys do not work after altering table/column names

From
Tom Lane
Date:
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
> Tom Lane wrote:
>> My first thought was some kind of callback function to construct the
>> query text afresh, but that seems pretty baroque.  It'd be simpler
>> to just have an option to let plancache.c return a failure indication
>> when its plan is obsolete, whereupon ri_triggers.c discards that plan
>> and builds a fresh one.  [ pokes around... ]  Hm, the fly in that
>> ointment is that ri_triggers and plancache aren't communicating directly
>> but through SPI.  I'm really loath to change the SPI API for this;
>> is there another way?

> ri_triggers.c could register a callback to invalidate entries in its
> hash table with CacheRegisterRelcacheCallback() ?

> Adding a new function, say SPI_is_plan_valid(plan), doesn't seem too bad
> to me either.

It's actually looking kind of nasty.  We don't know for sure that the
plan is still valid until RevalidateCachedPlan has succeeded in locking
all the tables involved.  (We could look to see if it's already invalid
at some earlier point, but that just leaves us with race conditions.)
And exposing that part of the procedure to a SPI caller would be pretty
horrid --- note the comments in spi.c about not wanting any daylight between
RevalidateCachedPlan and PortalDefineQuery.

The callback solution won't be any fun either.  Aside from being a bit
baroque, it would require nontrivial code refactoring in ri_triggers.c
to pull the query text generation code out into separate subroutines.

One possibility is that it looks like all the RI triggers actually
acquire locks on both relations before they do anything interesting.
So maybe there really wouldn't be any race condition if we execute an
is_plan_valid probe at the time of ri_FetchPreparedPlan.  It's a bit
shaky but might be better than trying to solve it in a "cleaner"
fashion; certainly anything more invasive than that wouldn't seem like
a good candidate for back-patching.

            regards, tom lane