aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/tablecmds.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2023-01-11 15:55:02 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2023-01-11 15:55:02 -0500
commit8bf6ec3ba3a44448817af47a080587f3b71bee08 (patch)
tree77dc9c8e2ad34fe6a173d5271cc4584ca26830b4 /src/backend/commands/tablecmds.c
parentd0d968328794a25c844dc57585d0289a08159bb5 (diff)
downloadpostgresql-8bf6ec3ba3a44448817af47a080587f3b71bee08.tar.gz
postgresql-8bf6ec3ba3a44448817af47a080587f3b71bee08.zip
Improve handling of inherited GENERATED expressions.
In both partitioning and traditional inheritance, require child columns to be GENERATED if and only if their parent(s) are. Formerly we allowed the case of an inherited column being GENERATED when its parent isn't, but that results in inconsistent behavior: the column can be directly updated through an UPDATE on the parent table, leading to it containing a user-supplied value that might not match the generation expression. This also fixes an oversight that we enforced partition-key-columns-can't- be-GENERATED against parent tables, but not against child tables that were dynamically attached to them. Also, remove the restriction that the child's generation expression be equivalent to the parent's. In the wake of commit 3f7836ff6, there doesn't seem to be any reason that we need that restriction, since generation expressions are always computed per-table anyway. By removing this, we can also allow a child to merge multiple inheritance parents with inconsistent generation expressions, by overriding them with its own expression, much as we've long allowed for DEFAULT expressions. Since we're rejecting a case that we used to accept, this doesn't seem like a back-patchable change. Given the lack of field complaints about the inconsistent behavior, it's likely that no one is doing this anyway, but we won't change it in minor releases. Amit Langote and Tom Lane Discussion: https://postgr.es/m/2793383.1672944799@sss.pgh.pa.us
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r--src/backend/commands/tablecmds.c124
1 files changed, 46 insertions, 78 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1db3bd9e2e8..1fbdad4b649 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2320,8 +2320,12 @@ storage_name(char c)
* (3) If conflicting defaults are inherited from different parents
* (and not overridden by the child), an error is raised.
* (4) Otherwise the inherited default is used.
- * Rule (3) is new in Postgres 7.1; in earlier releases you got a
- * rather arbitrary choice of which parent default to use.
+ *
+ * Note that the default-value infrastructure is used for generated
+ * columns' expressions too, so most of the preceding paragraph applies
+ * to generation expressions too. We insist that a child column be
+ * generated if and only if its parent(s) are, but it need not have
+ * the same generation expression.
*----------
*/
static List *
@@ -2659,7 +2663,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
}
/*
- * Locate default if any
+ * Locate default/generation expression if any
*/
if (attribute->atthasdef)
{
@@ -2923,23 +2927,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Check for conflicts related to generated columns.
*
- * If the parent column is generated, the child column must be
- * unadorned and will be made a generated column. (We could
- * in theory allow the child column definition specifying the
- * exact same generation expression, but that's a bit
- * complicated to implement and doesn't seem very useful.) We
- * also check that the child column doesn't specify a default
- * value or identity, which matches the rules for a single
- * column in parse_util.c.
+ * If the parent column is generated, the child column will be
+ * made a generated column if it isn't already. If it is a
+ * generated column, we'll take its generation expression in
+ * preference to the parent's. We must check that the child
+ * column doesn't specify a default value or identity, which
+ * matches the rules for a single column in parse_util.c.
+ *
+ * Conversely, if the parent column is not generated, the
+ * child column can't be either. (We used to allow that, but
+ * it results in being able to override the generation
+ * expression via UPDATEs through the parent.)
*/
if (def->generated)
{
- if (newdef->generated)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
- errmsg("child column \"%s\" specifies generation expression",
- def->colname),
- errhint("Omit the generation expression in the definition of the child table column to inherit the generation expression from the parent table.")));
if (newdef->raw_default && !newdef->generated)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
@@ -2951,15 +2952,14 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
errmsg("column \"%s\" inherits from generated column but specifies identity",
def->colname)));
}
-
- /*
- * If the parent column is not generated, then take whatever
- * the child column definition says.
- */
else
{
if (newdef->generated)
- def->generated = newdef->generated;
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ errmsg("child column \"%s\" specifies generation expression",
+ def->colname),
+ errhint("A child table column cannot be generated unless its parent column is.")));
}
/* If new def has a default, override previous default */
@@ -2994,8 +2994,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Now that we have the column definition list for a partition, we can
* check whether the columns referenced in the column constraint specs
- * actually exist. Also, we merge NOT NULL and defaults into each
- * corresponding column definition.
+ * actually exist. Also, we merge parent's NOT NULL constraints and
+ * defaults into each corresponding column definition.
*/
if (is_partition)
{
@@ -3015,6 +3015,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_not_null |= restdef->is_not_null;
/*
+ * As above, reject generated columns in partitions that
+ * are not generated in the parent.
+ */
+ if (restdef->generated && !coldef->generated)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ errmsg("child column \"%s\" specifies generation expression",
+ restdef->colname),
+ errhint("A child table column cannot be generated unless its parent column is.")));
+ /* Other way around should have been dealt with above */
+ Assert(!(coldef->generated && !restdef->generated));
+
+ /*
* Override the parent's default value for this column
* (coldef->cooked_default) with the partition's local
* definition (restdef->raw_default), if there's one. It
@@ -3058,7 +3071,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
errmsg("column \"%s\" inherits conflicting generation expressions",
- def->colname)));
+ def->colname),
+ errhint("To resolve the conflict, specify a generation expression explicitly.")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
@@ -15038,64 +15052,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attributeName)));
/*
- * If parent column is generated, child column must be, too.
+ * Child column must be generated if and only if parent column is.
*/
if (attribute->attgenerated && !childatt->attgenerated)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" in child table must be a generated column",
attributeName)));
-
- /*
- * Check that both generation expressions match.
- *
- * The test we apply is to see whether they reverse-compile to the
- * same source string. This insulates us from issues like whether
- * attributes have the same physical column numbers in parent and
- * child relations. (See also constraints_equivalent().)
- */
- if (attribute->attgenerated && childatt->attgenerated)
- {
- TupleConstr *child_constr = child_rel->rd_att->constr;
- TupleConstr *parent_constr = parent_rel->rd_att->constr;
- char *child_expr = NULL;
- char *parent_expr = NULL;
-
- Assert(child_constr != NULL);
- Assert(parent_constr != NULL);
-
- for (int i = 0; i < child_constr->num_defval; i++)
- {
- if (child_constr->defval[i].adnum == childatt->attnum)
- {
- child_expr =
- TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
- CStringGetTextDatum(child_constr->defval[i].adbin),
- ObjectIdGetDatum(child_rel->rd_id)));
- break;
- }
- }
- Assert(child_expr != NULL);
-
- for (int i = 0; i < parent_constr->num_defval; i++)
- {
- if (parent_constr->defval[i].adnum == attribute->attnum)
- {
- parent_expr =
- TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
- CStringGetTextDatum(parent_constr->defval[i].adbin),
- ObjectIdGetDatum(parent_rel->rd_id)));
- break;
- }
- }
- Assert(parent_expr != NULL);
-
- if (strcmp(child_expr, parent_expr) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table has a conflicting generation expression",
- attributeName)));
- }
+ if (childatt->attgenerated && !attribute->attgenerated)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("column \"%s\" in child table must not be a generated column",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail