fix-missing-Var-remaps-with-LIKE-and-inheritance.patch
text/x-diff
Filename: fix-missing-Var-remaps-with-LIKE-and-inheritance.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| src/backend/commands/tablecmds.c | 185 | 34 |
| src/test/regress/expected/create_table_like.out | 30 | 1 |
| src/test/regress/sql/create_table_like.sql | 13 | 1 |
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5745cd6..7aa1a18 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -310,8 +310,10 @@ static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
static void truncate_check_activity(Relation rel);
static void RangeVarCallbackForTruncate(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
-static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- bool is_partition, List **supconstr);
+static List *MergeAttributes(List *schema, List *supers,
+ List *schema_constraints,
+ char relpersistence, bool is_partition,
+ List **supconstr);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -774,10 +776,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes. (Note that stmt->tableElts is destructively
- * modified by MergeAttributes.)
+ * modified by MergeAttributes.) This step is also responsible for
+ * adjusting default and constraint expressions as needed to reflect any
+ * column renumbering that happens due to inheritance. (While "raw"
+ * expressions don't need adjustment, pre-"cooked" ones do.)
*/
stmt->tableElts =
MergeAttributes(stmt->tableElts, inheritOids,
+ stmt->constraints,
stmt->relation->relpersistence,
stmt->partbound != NULL,
&old_constraints);
@@ -2054,8 +2060,11 @@ storage_name(char c)
* 'schema' is the column/attribute definition for the table. (It's a list
* of ColumnDef's.) It is destructively changed.
* 'supers' is a list of OIDs of parent relations, already locked by caller.
- * 'relpersistence' is a persistence type of the table.
- * 'is_partition' tells if the table is a partition
+ * 'schema_constraints' is the list of constraints for the table. If there
+ * are any pre-cooked expressions in that, we'll update them as needed
+ * to account for possible column renumbering.
+ * 'relpersistence' is the persistence type of the table.
+ * 'is_partition' tells if the table is a partition.
*
* Output arguments:
* 'supconstr' receives a list of constraints belonging to the parents,
@@ -2104,8 +2113,9 @@ storage_name(char c)
*----------
*/
static List *
-MergeAttributes(List *schema, List *supers, char relpersistence,
- bool is_partition, List **supconstr)
+MergeAttributes(List *schema, List *supers, List *schema_constraints,
+ char relpersistence, bool is_partition,
+ List **supconstr)
{
List *inhSchema = NIL;
List *constraints = NIL;
@@ -2218,7 +2228,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
TupleDesc tupleDesc;
TupleConstr *constr;
AttrMap *newattmap;
+ List *inherited_defaults;
+ List *cols_with_defaults;
AttrNumber parent_attno;
+ ListCell *lc1;
+ ListCell *lc2;
/* caller already got lock */
relation = table_open(parent, NoLock);
@@ -2304,6 +2318,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
newattmap = make_attrmap(tupleDesc->natts);
+ /* We can't process inherited defaults until newattmap is complete. */
+ inherited_defaults = cols_with_defaults = NIL;
+
for (parent_attno = 1; parent_attno <= tupleDesc->natts;
parent_attno++)
{
@@ -2359,7 +2376,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
get_collation_name(defCollId),
get_collation_name(attribute->attcollation))));
- /* Copy storage parameter */
+ /* Copy/check storage parameter */
if (def->storage == 0)
def->storage = attribute->attstorage;
else if (def->storage != attribute->attstorage)
@@ -2371,18 +2388,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(attribute->attstorage))));
- def->inhcount++;
- /* Merge of NOT NULL constraints = OR 'em together */
- def->is_not_null |= attribute->attnotnull;
- /* Default and other constraints are handled below */
- newattmap->attnums[parent_attno - 1] = exist_attno;
-
/* Check for GENERATED conflicts */
if (def->generated != attribute->attgenerated)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("inherited column \"%s\" has a generation conflict",
attributeName)));
+
+ def->inhcount++;
+ /* Merge of NOT NULL constraints = OR 'em together */
+ def->is_not_null |= attribute->attnotnull;
+ /* Default and other constraints are handled below */
+ newattmap->attnums[parent_attno - 1] = exist_attno;
}
else
{
@@ -2410,7 +2427,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
}
/*
- * Copy default if any
+ * Locate default if any
*/
if (attribute->atthasdef)
{
@@ -2432,23 +2449,58 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Assert(this_default != NULL);
/*
- * If default expr could contain any vars, we'd need to fix
- * 'em, but it can't; so default is ready to apply to child.
- *
- * If we already had a default from some prior parent, check
- * to see if they are the same. If so, no problem; if not,
- * mark the column as having a bogus default. Below, we will
- * complain if the bogus default isn't overridden by the child
- * schema.
+ * If it's a GENERATED default, it might contain Vars that
+ * need to be mapped to the inherited column(s)' new numbers.
+ * We can't do that till newattmap is ready, so just remember
+ * all the inherited default expressions for the moment.
*/
- Assert(def->raw_default == NULL);
- if (def->cooked_default == NULL)
- def->cooked_default = this_default;
- else if (!equal(def->cooked_default, this_default))
- {
- def->cooked_default = &bogus_marker;
- have_bogus_defaults = true;
- }
+ inherited_defaults = lappend(inherited_defaults, this_default);
+ cols_with_defaults = lappend(cols_with_defaults, def);
+ }
+ }
+
+ /*
+ * Now process any inherited default expressions, adjusting attnos
+ * using the completed newattmap map.
+ */
+ forboth(lc1, inherited_defaults, lc2, cols_with_defaults)
+ {
+ Node *this_default = (Node *) lfirst(lc1);
+ ColumnDef *def = (ColumnDef *) lfirst(lc2);
+ bool found_whole_row;
+
+ /* Adjust Vars to match new table's column numbering */
+ this_default = map_variable_attnos(this_default,
+ 1, 0,
+ newattmap,
+ InvalidOid, &found_whole_row);
+
+ /*
+ * For the moment we have to reject whole-row variables. We could
+ * convert them, if we knew the new table's rowtype OID, but that
+ * hasn't been assigned yet.
+ */
+ if (found_whole_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert whole-row table reference"),
+ errdetail("Default for column \"%s\" contains a whole-row reference to table \"%s\".",
+ def->colname,
+ RelationGetRelationName(relation))));
+
+ /*
+ * If we already had a default from some prior parent, check to
+ * see if they are the same. If so, no problem; if not, mark the
+ * column as having a bogus default. Below, we will complain if
+ * the bogus default isn't overridden by the child schema.
+ */
+ Assert(def->raw_default == NULL);
+ if (def->cooked_default == NULL)
+ def->cooked_default = this_default;
+ else if (!equal(def->cooked_default, this_default))
+ {
+ def->cooked_default = &bogus_marker;
+ have_bogus_defaults = true;
}
}
@@ -2525,12 +2577,24 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
- * columns into the inherited schema list. Although, we never have any
- * explicitly declared columns if the table is a partition.
+ * columns into the inherited schema list. That also means that we might
+ * have to renumber Vars in any pre-cooked expressions in the schema.
*/
if (inhSchema != NIL)
{
int schema_attno = 0;
+ AttrMap *newattmap;
+ List *cols_with_defaults;
+ ListCell *lc1;
+
+ /*
+ * newattmap will contain the possibly-adjusted column numbers for the
+ * explicitly declared columns.
+ */
+ newattmap = make_attrmap(list_length(schema));
+
+ /* We can't process cooked defaults until newattmap is complete. */
+ cols_with_defaults = NIL;
foreach(entry, schema)
{
@@ -2614,15 +2678,21 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
+ /* OK, build the attmap */
+ newattmap->attnums[schema_attno - 1] = exist_attno;
+
/* Mark the column as locally defined */
def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
- if (newdef->raw_default != NULL)
+ if (newdef->raw_default || newdef->cooked_default)
{
def->raw_default = newdef->raw_default;
def->cooked_default = newdef->cooked_default;
+ /* We'll need to reprocess default, if it's cooked */
+ if (def->cooked_default)
+ cols_with_defaults = lappend(cols_with_defaults, def);
}
}
else
@@ -2631,6 +2701,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* No, attach new column to result schema
*/
inhSchema = lappend(inhSchema, newdef);
+
+ /* Build the attmap */
+ newattmap->attnums[schema_attno - 1] = list_length(inhSchema);
+
+ /* We'll need to reprocess default, if it's cooked */
+ if (newdef->cooked_default)
+ cols_with_defaults = lappend(cols_with_defaults, newdef);
}
}
@@ -2645,6 +2722,80 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("tables can have at most %d columns",
MaxHeapAttributeNumber)));
+
+ /*
+ * If any columns have cooked defaults that came from the declared
+ * schema, update Var numbers in those expressions. (This can only
+ * happen with defaults coming from LIKE clauses, since only those
+ * could be pre-cooked at this stage.)
+ */
+ foreach(lc1, cols_with_defaults)
+ {
+ ColumnDef *def = (ColumnDef *) lfirst(lc1);
+ bool found_whole_row;
+
+ def->cooked_default = map_variable_attnos(def->cooked_default,
+ 1, 0,
+ newattmap,
+ InvalidOid,
+ &found_whole_row);
+
+ /*
+ * For the moment we have to reject whole-row variables. We could
+ * convert them, if we knew the new table's rowtype OID, but that
+ * hasn't been assigned yet.
+ */
+ if (found_whole_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert whole-row table reference"),
+ errdetail("Default for column \"%s\" contains a whole-row reference.",
+ def->colname)));
+ }
+
+ /*
+ * Likewise, any pre-cooked expressions in CHECK constraints in the
+ * declared schema need to be adjusted. (Again, LIKE is to blame for
+ * needing to do this.)
+ */
+ foreach(lc1, schema_constraints)
+ {
+ Constraint *cdef = (Constraint *) lfirst(lc1);
+ Node *expr;
+ bool found_whole_row;
+
+ if (cdef->contype != CONSTR_CHECK)
+ continue;
+
+ if (cdef->cooked_expr == NULL)
+ continue;
+
+ expr = stringToNode(cdef->cooked_expr);
+
+ expr = map_variable_attnos(expr,
+ 1, 0,
+ newattmap,
+ InvalidOid,
+ &found_whole_row);
+
+ /*
+ * For the moment we have to reject whole-row variables. We could
+ * convert them, if we knew the new table's rowtype OID, but that
+ * hasn't been assigned yet. (Note: there should probably always
+ * be a constraint name available at this point, so we don't try
+ * too hard to deal with the case where there isn't.)
+ */
+ if (found_whole_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert whole-row table reference"),
+ errdetail("Constraint \"%s\" contains a whole-row reference.",
+ cdef->conname ? cdef->conname : "(unnamed)")));
+
+ cdef->cooked_expr = nodeToString(expr);
+ }
+
+ free_attrmap(newattmap);
}
/*
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 655e8e4..6989dd7 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -160,7 +160,9 @@ SELECT * FROM test_like_gen_3;
DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
-- also test generated column with a "forward" reference (bug #16342)
-CREATE TABLE test_like_4 (b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) STORED, a int);
+CREATE TABLE test_like_4 (b int DEFAULT 42,
+ c int GENERATED ALWAYS AS (a * 2) STORED,
+ a int CHECK (a > 0));
\d test_like_4
Table "public.test_like_4"
Column | Type | Collation | Nullable | Default
@@ -168,6 +170,8 @@ CREATE TABLE test_like_4 (b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) ST
b | integer | | | 42
c | integer | | | generated always as (a * 2) stored
a | integer | | |
+Check constraints:
+ "test_like_4_a_check" CHECK (a > 0)
CREATE TABLE test_like_4a (LIKE test_like_4);
CREATE TABLE test_like_4b (LIKE test_like_4 INCLUDING DEFAULTS);
@@ -233,7 +237,32 @@ SELECT a, b, c FROM test_like_4d;
11 | 42 | 22
(1 row)
+-- Test renumbering of Vars when combining LIKE with inheritance
+CREATE TABLE test_like_5 (x point, y point, z point);
+CREATE TABLE test_like_5x (p int CHECK (p > 0),
+ q int GENERATED ALWAYS AS (p * 2) STORED);
+CREATE TABLE test_like_5c (LIKE test_like_4 INCLUDING ALL)
+ INHERITS (test_like_5, test_like_5x);
+\d test_like_5c
+ Table "public.test_like_5c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ x | point | | |
+ y | point | | |
+ z | point | | |
+ p | integer | | |
+ q | integer | | | generated always as (p * 2) stored
+ b | integer | | | 42
+ c | integer | | | generated always as (a * 2) stored
+ a | integer | | |
+Check constraints:
+ "test_like_4_a_check" CHECK (a > 0)
+ "test_like_5x_p_check" CHECK (p > 0)
+Inherits: test_like_5,
+ test_like_5x
+
DROP TABLE test_like_4, test_like_4a, test_like_4b, test_like_4c, test_like_4d;
+DROP TABLE test_like_5, test_like_5x, test_like_5c;
CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */
INSERT INTO inhg VALUES (5, 10);
INSERT INTO inhg VALUES (20, 10); -- should fail
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 6981ac0..708948b 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -66,7 +66,9 @@ SELECT * FROM test_like_gen_3;
DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
-- also test generated column with a "forward" reference (bug #16342)
-CREATE TABLE test_like_4 (b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) STORED, a int);
+CREATE TABLE test_like_4 (b int DEFAULT 42,
+ c int GENERATED ALWAYS AS (a * 2) STORED,
+ a int CHECK (a > 0));
\d test_like_4
CREATE TABLE test_like_4a (LIKE test_like_4);
CREATE TABLE test_like_4b (LIKE test_like_4 INCLUDING DEFAULTS);
@@ -84,7 +86,17 @@ SELECT a, b, c FROM test_like_4c;
\d test_like_4d
INSERT INTO test_like_4d (a) VALUES(11);
SELECT a, b, c FROM test_like_4d;
+
+-- Test renumbering of Vars when combining LIKE with inheritance
+CREATE TABLE test_like_5 (x point, y point, z point);
+CREATE TABLE test_like_5x (p int CHECK (p > 0),
+ q int GENERATED ALWAYS AS (p * 2) STORED);
+CREATE TABLE test_like_5c (LIKE test_like_4 INCLUDING ALL)
+ INHERITS (test_like_5, test_like_5x);
+\d test_like_5c
+
DROP TABLE test_like_4, test_like_4a, test_like_4b, test_like_4c, test_like_4d;
+DROP TABLE test_like_5, test_like_5x, test_like_5c;
CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */
INSERT INTO inhg VALUES (5, 10);