Fix usage of "tableoid" in GENERATED expressions.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 21 May 2021 19:02:06 +0000 (15:02 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 21 May 2021 19:02:06 +0000 (15:02 -0400)
We consider this supported (though I've got my doubts that it's a
good idea, because tableoid is not immutable).  However, several
code paths failed to fill the field in soon enough, causing such
a GENERATED expression to see zero or the wrong value.  This
occurred when ALTER TABLE adds a new GENERATED column to a table
with existing rows, and during regular INSERT or UPDATE on a
foreign table with GENERATED columns.

Noted during investigation of a report from Vitaly Ustinov.
Back-patch to v12 where GENERATED came in.

Discussion: https://postgr.es/m/CAM_DEiWR2DPT6U4xb-Ehigozzd3n3G37ZB1+867zbsEVtYoJww@mail.gmail.com

src/backend/commands/tablecmds.c
src/backend/executor/nodeModifyTable.c
src/test/regress/expected/generated.out
src/test/regress/sql/generated.sql

index ebc62034d26e9eb3fd10ba1903c6d14e702a87ba..85dcc330638aa910b1e6a515cd82489cf9790e4c 100644 (file)
@@ -5761,6 +5761,14 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
                                foreach(lc, dropped_attrs)
                                        newslot->tts_isnull[lfirst_int(lc)] = true;
 
+                               /*
+                                * Constraints and GENERATED expressions might reference the
+                                * tableoid column, so fill tts_tableOid with the desired
+                                * value.  (We must do this each time, because it gets
+                                * overwritten with newrel's OID during storing.)
+                                */
+                               newslot->tts_tableOid = RelationGetRelid(oldrel);
+
                                /*
                                 * Process supplied expressions to replace selected columns.
                                 *
@@ -5804,11 +5812,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
                                                                           &newslot->tts_isnull[ex->attnum - 1]);
                                }
 
-                               /*
-                                * Constraints might reference the tableoid column, so
-                                * initialize t_tableOid before evaluating them.
-                                */
-                               newslot->tts_tableOid = RelationGetRelid(oldrel);
                                insertslot = newslot;
                        }
                        else
index 0816027f7f7c7b4be8f8cdcaaa3b657d7e62628c..379b05631050c333a0aee05b187a3eabc63fd97c 100644 (file)
@@ -658,6 +658,12 @@ ExecInsert(ModifyTableState *mtstate,
        }
        else if (resultRelInfo->ri_FdwRoutine)
        {
+               /*
+                * GENERATED expressions might reference the tableoid column, so
+                * (re-)initialize tts_tableOid before evaluating them.
+                */
+               slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+
                /*
                 * Compute stored generated columns
                 */
@@ -729,7 +735,7 @@ ExecInsert(ModifyTableState *mtstate,
                /*
                 * AFTER ROW Triggers or RETURNING expressions might reference the
                 * tableoid column, so (re-)initialize tts_tableOid before evaluating
-                * them.
+                * them.  (This covers the case where the FDW replaced the slot.)
                 */
                slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
        }
@@ -738,8 +744,8 @@ ExecInsert(ModifyTableState *mtstate,
                WCOKind         wco_kind;
 
                /*
-                * Constraints might reference the tableoid column, so (re-)initialize
-                * tts_tableOid before evaluating them.
+                * Constraints and GENERATED expressions might reference the tableoid
+                * column, so (re-)initialize tts_tableOid before evaluating them.
                 */
                slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 
@@ -1629,6 +1635,12 @@ ExecUpdate(ModifyTableState *mtstate,
        }
        else if (resultRelInfo->ri_FdwRoutine)
        {
+               /*
+                * GENERATED expressions might reference the tableoid column, so
+                * (re-)initialize tts_tableOid before evaluating them.
+                */
+               slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+
                /*
                 * Compute stored generated columns
                 */
@@ -1651,7 +1663,7 @@ ExecUpdate(ModifyTableState *mtstate,
                /*
                 * AFTER ROW Triggers or RETURNING expressions might reference the
                 * tableoid column, so (re-)initialize tts_tableOid before evaluating
-                * them.
+                * them.  (This covers the case where the FDW replaced the slot.)
                 */
                slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
        }
@@ -1662,8 +1674,8 @@ ExecUpdate(ModifyTableState *mtstate,
                bool            update_indexes;
 
                /*
-                * Constraints might reference the tableoid column, so (re-)initialize
-                * tts_tableOid before evaluating them.
+                * Constraints and GENERATED expressions might reference the tableoid
+                * column, so (re-)initialize tts_tableOid before evaluating them.
                 */
                slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 
index 675773f0c1f28573cd20a31d208eb94f89a4d7ee..71542e95d5f0a6453270ae215bbbee8fb027ce79 100644 (file)
@@ -64,6 +64,7 @@ ERROR:  both identity and generation expression specified for column "b" of tabl
 LINE 1: ...t PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ...
                                                              ^
 -- reference to system column not allowed in generated column
+-- (except tableoid, which we test below)
 CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED);
 ERROR:  cannot use system column "xmin" in column generation expression
 LINE 1: ...a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37...
@@ -455,14 +456,16 @@ DROP TYPE double_int;
 -- using tableoid is allowed
 CREATE TABLE gtest_tableoid (
   a int PRIMARY KEY,
-  b bool GENERATED ALWAYS AS (tableoid <> 0) STORED
+  b bool GENERATED ALWAYS AS (tableoid = 'gtest_tableoid'::regclass) STORED
 );
 INSERT INTO gtest_tableoid VALUES (1), (2);
+ALTER TABLE gtest_tableoid ADD COLUMN
+  c regclass GENERATED ALWAYS AS (tableoid) STORED;
 SELECT * FROM gtest_tableoid;
- a | b 
----+---
- 1 | t
- 2 | t
+ a | b |       c        
+---+---+----------------
+ 1 | t | gtest_tableoid
+ 2 | t | gtest_tableoid
 (2 rows)
 
 -- drop column behavior
index 63251c443a968a6c27db0d33d76b298776b3de81..914197608b749b39774666a2a8fb4a8e71fd5562 100644 (file)
@@ -29,6 +29,7 @@ CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS A
 CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED);
 
 -- reference to system column not allowed in generated column
+-- (except tableoid, which we test below)
 CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED);
 
 -- various prohibited constructs
@@ -218,9 +219,11 @@ DROP TYPE double_int;
 -- using tableoid is allowed
 CREATE TABLE gtest_tableoid (
   a int PRIMARY KEY,
-  b bool GENERATED ALWAYS AS (tableoid <> 0) STORED
+  b bool GENERATED ALWAYS AS (tableoid = 'gtest_tableoid'::regclass) STORED
 );
 INSERT INTO gtest_tableoid VALUES (1), (2);
+ALTER TABLE gtest_tableoid ADD COLUMN
+  c regclass GENERATED ALWAYS AS (tableoid) STORED;
 SELECT * FROM gtest_tableoid;
 
 -- drop column behavior