aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands')
-rw-r--r--src/backend/commands/analyze.c4
-rw-r--r--src/backend/commands/indexcmds.c33
-rw-r--r--src/backend/commands/publicationcmds.c23
-rw-r--r--src/backend/commands/statscmds.c20
-rw-r--r--src/backend/commands/tablecmds.c196
-rw-r--r--src/backend/commands/trigger.c45
6 files changed, 279 insertions, 42 deletions
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f8da32e9aef..e5ab207d2ec 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1039,6 +1039,10 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr)
if (attr->attisdropped)
return NULL;
+ /* Don't analyze virtual generated columns */
+ if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ return NULL;
+
/*
* Get attstattarget value. Set to -1 if null. (Analyze functions expect
* -1 to mean use default_statistics_target; see for example
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 5b1753d4681..f8d3ea820e1 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1110,6 +1110,9 @@ DefineIndex(Oid tableId,
/*
* We disallow indexes on system columns. They would not necessarily get
* updated correctly, and they don't seem useful anyway.
+ *
+ * Also disallow virtual generated columns in indexes (use expression
+ * index instead).
*/
for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
@@ -1119,14 +1122,24 @@ DefineIndex(Oid tableId,
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
+
+
+ if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ stmt->isconstraint ?
+ errmsg("unique constraints on virtual generated columns are not supported") :
+ errmsg("indexes on virtual generated columns are not supported")));
}
/*
- * Also check for system columns used in expressions or predicates.
+ * Also check for system and generated columns used in expressions or
+ * predicates.
*/
if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
{
Bitmapset *indexattrs = NULL;
+ int j;
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
@@ -1139,6 +1152,24 @@ DefineIndex(Oid tableId,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
}
+
+ /*
+ * XXX Virtual generated columns in index expressions or predicates
+ * could be supported, but it needs support in
+ * RelationGetIndexExpressions() and RelationGetIndexPredicate().
+ */
+ j = -1;
+ while ((j = bms_next_member(indexattrs, j)) >= 0)
+ {
+ AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
+
+ if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ stmt->isconstraint ?
+ errmsg("unique constraints on virtual generated columns are not supported") :
+ errmsg("indexes on virtual generated columns are not supported")));
+ }
}
/* Is index safe for others to ignore? See set_indexsafe_procflags() */
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 951ffabb656..150a768d16f 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -38,6 +38,7 @@
#include "parser/parse_clause.h"
#include "parser/parse_collate.h"
#include "parser/parse_relation.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -404,6 +405,14 @@ pub_contains_invalid_column(Oid pubid, Relation relation, List *ancestors,
relation->rd_att->constr->has_generated_stored)
*invalid_gen_col = true;
+ /*
+ * Virtual generated columns are currently not supported for logical
+ * replication at all.
+ */
+ if (relation->rd_att->constr &&
+ relation->rd_att->constr->has_generated_virtual)
+ *invalid_gen_col = true;
+
if (*invalid_gen_col && *invalid_column_list)
return true;
}
@@ -430,7 +439,17 @@ pub_contains_invalid_column(Oid pubid, Relation relation, List *ancestors,
* The publish_generated_columns option must be set to stored if
* the REPLICA IDENTITY contains any stored generated column.
*/
- if (pubgencols_type != PUBLISH_GENCOLS_STORED && att->attgenerated)
+ if (att->attgenerated == ATTRIBUTE_GENERATED_STORED && pubgencols_type != PUBLISH_GENCOLS_STORED)
+ {
+ *invalid_gen_col = true;
+ break;
+ }
+
+ /*
+ * The equivalent setting for virtual generated columns does not
+ * exist yet.
+ */
+ if (att->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
{
*invalid_gen_col = true;
break;
@@ -689,6 +708,8 @@ TransformPubWhereClauses(List *tables, const char *queryString,
/* Fix up collation information */
assign_expr_collations(pstate, whereclause);
+ whereclause = expand_generated_columns_in_expr(whereclause, pri->relation, 1);
+
/*
* We allow only simple expressions in row filters. See
* check_simple_rowfilter_expr_walker.
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index a817821bf6d..e24d540cd45 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -246,6 +246,12 @@ CreateStatistics(CreateStatsStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("statistics creation on system columns is not supported")));
+ /* Disallow use of virtual generated columns in extended stats */
+ if (attForm->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("statistics creation on virtual generated columns is not supported")));
+
/* Disallow data types without a less-than operator */
type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
if (type->lt_opr == InvalidOid)
@@ -269,6 +275,12 @@ CreateStatistics(CreateStatsStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("statistics creation on system columns is not supported")));
+ /* Disallow use of virtual generated columns in extended stats */
+ if (get_attgenerated(relid, var->varattno) == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("statistics creation on virtual generated columns is not supported")));
+
/* Disallow data types without a less-than operator */
type = lookup_type_cache(var->vartype, TYPECACHE_LT_OPR);
if (type->lt_opr == InvalidOid)
@@ -290,7 +302,6 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(expr != NULL);
- /* Disallow expressions referencing system attributes. */
pull_varattnos(expr, 1, &attnums);
k = -1;
@@ -298,10 +309,17 @@ CreateStatistics(CreateStatsStmt *stmt)
{
AttrNumber attnum = k + FirstLowInvalidHeapAttributeNumber;
+ /* Disallow expressions referencing system attributes. */
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("statistics creation on system columns is not supported")));
+
+ /* Disallow use of virtual generated columns in extended stats */
+ if (get_attgenerated(relid, attnum) == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("statistics creation on virtual generated columns is not supported")));
}
/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 18f64db6e39..94660e4bd45 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3039,6 +3039,15 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
errhint("A child table column cannot be generated unless its parent column is.")));
}
+ if (coldef->generated && restdef->generated && coldef->generated != restdef->generated)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ errmsg("column \"%s\" inherits from generated column of different kind",
+ restdef->colname),
+ errdetail("Parent column is %s, child column is %s.",
+ coldef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
+ restdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
+
/*
* Override the parent's default value for this column
* (coldef->cooked_default) with the partition's local
@@ -3324,6 +3333,15 @@ MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const
errhint("A child table column cannot be generated unless its parent column is.")));
}
+ if (inhdef->generated && newdef->generated && newdef->generated != inhdef->generated)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ errmsg("column \"%s\" inherits from generated column of different kind",
+ inhdef->colname),
+ errdetail("Parent column is %s, child column is %s.",
+ inhdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
+ newdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
+
/*
* If new def has a default, override previous default
*/
@@ -6130,7 +6148,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
{
case CONSTR_CHECK:
needscan = true;
- con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate);
+ con->qualstate = ExecPrepareExpr((Expr *) expand_generated_columns_in_expr(con->qual, newrel ? newrel : oldrel, 1), estate);
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
@@ -7308,7 +7326,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
* DEFAULT value outside of the heap. This may be disabled inside
* AddRelationNewConstraints if the optimization cannot be applied.
*/
- rawEnt->missingMode = (!colDef->generated);
+ rawEnt->missingMode = (colDef->generated != ATTRIBUTE_GENERATED_STORED);
rawEnt->generated = colDef->generated;
@@ -7785,6 +7803,14 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
errmsg("cannot alter system column \"%s\"",
colName)));
+ /* TODO: see transformColumnDefinition() */
+ if (TupleDescAttr(RelationGetDescr(rel), attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not-null constraints are not supported on virtual generated columns"),
+ errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
+ colName, RelationGetRelationName(rel))));
+
/* See if there's already a constraint */
tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
if (HeapTupleIsValid(tuple))
@@ -8411,6 +8437,8 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
HeapTuple tuple;
Form_pg_attribute attTup;
AttrNumber attnum;
+ char attgenerated;
+ bool rewrite;
Oid attrdefoid;
ObjectAddress address;
Expr *defval;
@@ -8425,36 +8453,70 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
colName, RelationGetRelationName(rel))));
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
- attnum = attTup->attnum;
+ attnum = attTup->attnum;
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
- if (attTup->attgenerated != ATTRIBUTE_GENERATED_STORED)
+ attgenerated = attTup->attgenerated;
+ if (!attgenerated)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" is not a generated column",
colName, RelationGetRelationName(rel))));
- ReleaseSysCache(tuple);
/*
- * Clear all the missing values if we're rewriting the table, since this
- * renders them pointless.
+ * TODO: This could be done, just need to recheck any constraints
+ * afterwards.
*/
- RelationClearMissing(rel);
-
- /* make sure we don't conflict with later attribute modifications */
- CommandCounterIncrement();
+ if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
+ rel->rd_att->constr && rel->rd_att->constr->num_check > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns on tables with check constraints"),
+ errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
+ colName, RelationGetRelationName(rel))));
/*
- * Find everything that depends on the column (constraints, indexes, etc),
- * and record enough information to let us recreate the objects after
- * rewrite.
+ * We need to prevent this because a change of expression could affect a
+ * row filter and inject expressions that are not permitted in a row
+ * filter. XXX We could try to have a more precise check to catch only
+ * publications with row filters, or even re-verify the row filter
+ * expressions.
*/
- RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
+ if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
+ GetRelationPublications(RelationGetRelid(rel)) != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns on tables that are part of a publication"),
+ errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
+ colName, RelationGetRelationName(rel))));
+
+ rewrite = (attgenerated == ATTRIBUTE_GENERATED_STORED);
+
+ ReleaseSysCache(tuple);
+
+ if (rewrite)
+ {
+ /*
+ * Clear all the missing values if we're rewriting the table, since
+ * this renders them pointless.
+ */
+ RelationClearMissing(rel);
+
+ /* make sure we don't conflict with later attribute modifications */
+ CommandCounterIncrement();
+
+ /*
+ * Find everything that depends on the column (constraints, indexes,
+ * etc), and record enough information to let us recreate the objects
+ * after rewrite.
+ */
+ RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
+ }
/*
* Drop the dependency records of the GENERATED expression, in particular
@@ -8483,7 +8545,7 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
rawEnt->attnum = attnum;
rawEnt->raw_default = newExpr;
rawEnt->missingMode = false;
- rawEnt->generated = ATTRIBUTE_GENERATED_STORED;
+ rawEnt->generated = attgenerated;
/* Store the generated expression */
AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
@@ -8492,16 +8554,19 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
/* Make above new expression visible */
CommandCounterIncrement();
- /* Prepare for table rewrite */
- defval = (Expr *) build_column_default(rel, attnum);
+ if (rewrite)
+ {
+ /* Prepare for table rewrite */
+ defval = (Expr *) build_column_default(rel, attnum);
- newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
- newval->attnum = attnum;
- newval->expr = expression_planner(defval);
- newval->is_generated = true;
+ newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
+ newval->attnum = attnum;
+ newval->expr = expression_planner(defval);
+ newval->is_generated = true;
- tab->newvals = lappend(tab->newvals, newval);
- tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+ tab->newvals = lappend(tab->newvals, newval);
+ tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+ }
/* Drop any pg_statistic entry for the column */
RemoveStatistics(RelationGetRelid(rel), attnum);
@@ -8590,17 +8655,30 @@ ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMOD
errmsg("cannot alter system column \"%s\"",
colName)));
- if (attTup->attgenerated != ATTRIBUTE_GENERATED_STORED)
+ /*
+ * TODO: This could be done, but it would need a table rewrite to
+ * materialize the generated values. Note that for the time being, we
+ * still error with missing_ok, so that we don't silently leave the column
+ * as generated.
+ */
+ if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"),
+ errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
+ colName, RelationGetRelationName(rel))));
+
+ if (!attTup->attgenerated)
{
if (!missing_ok)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("column \"%s\" of relation \"%s\" is not a stored generated column",
+ errmsg("column \"%s\" of relation \"%s\" is not a generated column",
colName, RelationGetRelationName(rel))));
else
{
ereport(NOTICE,
- (errmsg("column \"%s\" of relation \"%s\" is not a stored generated column, skipping",
+ (errmsg("column \"%s\" of relation \"%s\" is not a generated column, skipping",
colName, RelationGetRelationName(rel))));
heap_freetuple(tuple);
table_close(attrelation, RowExclusiveLock);
@@ -8743,6 +8821,16 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
errmsg("cannot alter system column \"%s\"",
colName)));
+ /*
+ * Prevent this as long as the ANALYZE code skips virtual generated
+ * columns.
+ */
+ if (attrtuple->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter statistics on virtual generated column \"%s\"",
+ colName)));
+
if (rel->rd_rel->relkind == RELKIND_INDEX ||
rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
{
@@ -9925,6 +10013,19 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
errmsg("invalid %s action for foreign key constraint containing generated column",
"ON DELETE")));
}
+
+ /*
+ * FKs on virtual columns are not supported. This would require
+ * various additional support in ri_triggers.c, including special
+ * handling in ri_NullCheck(), ri_KeysEqual(),
+ * RI_FKey_fk_upd_check_required() (since all virtual columns appear
+ * as NULL there). Also not really practical as long as you can't
+ * index virtual columns.
+ */
+ if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints on virtual generated columns are not supported")));
}
/*
@@ -12289,7 +12390,7 @@ QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
val = SysCacheGetAttrNotNull(CONSTROID, contuple,
Anum_pg_constraint_conbin);
conbin = TextDatumGetCString(val);
- newcon->qual = (Node *) stringToNode(conbin);
+ newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);
/* Find or create work queue entry for this table */
tab = ATGetQueueEntry(wqueue, rel);
@@ -13467,8 +13568,12 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
0);
- if (tab->relkind == RELKIND_RELATION ||
- tab->relkind == RELKIND_PARTITIONED_TABLE)
+ if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ /* do nothing */
+ }
+ else if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -13541,11 +13646,12 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("\"%s\" is not a table",
RelationGetRelationName(rel))));
- if (!RELKIND_HAS_STORAGE(tab->relkind))
+ if (!RELKIND_HAS_STORAGE(tab->relkind) || attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
{
/*
- * For relations without storage, do this check now. Regular tables
- * will check it later when the table is being rewritten.
+ * For relations or columns without storage, do this check now.
+ * Regular tables will check it later when the table is being
+ * rewritten.
*/
find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
}
@@ -16534,6 +16640,14 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispart
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" in child table must not be a generated column", parent_attname)));
+ if (parent_att->attgenerated && child_att->attgenerated && child_att->attgenerated != parent_att->attgenerated)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("column \"%s\" inherits from generated column of different kind", parent_attname),
+ errdetail("Parent column is %s, child column is %s.",
+ parent_att->attgenerated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
+ child_att->attgenerated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
+
/*
* Regular inheritance children are independent enough not to
* inherit identity columns. But partitions are integral part of
@@ -18781,8 +18895,11 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
parser_errposition(pstate, pelem->location)));
/*
- * Generated columns cannot work: They are computed after BEFORE
- * triggers, but partition routing is done before all triggers.
+ * Stored generated columns cannot work: They are computed after
+ * BEFORE triggers, but partition routing is done before all
+ * triggers. Maybe virtual generated columns could be made to
+ * work, but then they would need to be handled as an expression
+ * below.
*/
if (attform->attgenerated)
ereport(ERROR,
@@ -18864,9 +18981,12 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
}
/*
- * Generated columns cannot work: They are computed after
- * BEFORE triggers, but partition routing is done before all
- * triggers.
+ * Stored generated columns cannot work: They are computed
+ * after BEFORE triggers, but partition routing is done before
+ * all triggers. Virtual generated columns could probably
+ * work, but it would require more work elsewhere (for example
+ * SET EXPRESSION would need to check whether the column is
+ * used in partition keys). Seems safer to prohibit for now.
*/
i = -1;
while ((i = bms_next_member(expr_attrs, i)) >= 0)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 7a5ffe32f60..97c087929f3 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -43,6 +43,7 @@
#include "parser/parse_relation.h"
#include "partitioning/partdesc.h"
#include "pgstat.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -101,6 +102,7 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
bool is_crosspart_update);
static void AfterTriggerEnlargeQueryState(void);
static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
+static HeapTuple check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple tuple);
/*
@@ -641,7 +643,8 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
if (TRIGGER_FOR_BEFORE(tgtype) &&
var->varattno == 0 &&
RelationGetDescr(rel)->constr &&
- RelationGetDescr(rel)->constr->has_generated_stored)
+ (RelationGetDescr(rel)->constr->has_generated_stored ||
+ RelationGetDescr(rel)->constr->has_generated_virtual))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
@@ -2504,6 +2507,8 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
}
else if (newtuple != oldtuple)
{
+ newtuple = check_modified_virtual_generated(RelationGetDescr(relinfo->ri_RelationDesc), newtuple);
+
ExecForceStoreHeapTuple(newtuple, slot, false);
/*
@@ -3061,6 +3066,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
}
else if (newtuple != oldtuple)
{
+ newtuple = check_modified_virtual_generated(RelationGetDescr(relinfo->ri_RelationDesc), newtuple);
+
ExecForceStoreHeapTuple(newtuple, newslot, false);
/*
@@ -3491,6 +3498,8 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
tgqual = stringToNode(trigger->tgqual);
+ tgqual = expand_generated_columns_in_expr(tgqual, relinfo->ri_RelationDesc, PRS2_OLD_VARNO);
+ tgqual = expand_generated_columns_in_expr(tgqual, relinfo->ri_RelationDesc, PRS2_NEW_VARNO);
/* Change references to OLD and NEW to INNER_VAR and OUTER_VAR */
ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER_VAR, 0);
ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0);
@@ -6621,3 +6630,37 @@ pg_trigger_depth(PG_FUNCTION_ARGS)
{
PG_RETURN_INT32(MyTriggerDepth);
}
+
+/*
+ * Check whether a trigger modified a virtual generated column and replace the
+ * value with null if so.
+ *
+ * We need to check this so that we don't end up storing a non-null value in a
+ * virtual generated column.
+ *
+ * We don't need to check for stored generated columns, since those will be
+ * overwritten later anyway.
+ */
+static HeapTuple
+check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple tuple)
+{
+ if (!(tupdesc->constr && tupdesc->constr->has_generated_virtual))
+ return tuple;
+
+ for (int i = 0; i < tupdesc->natts; i++)
+ {
+ if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ if (!heap_attisnull(tuple, i + 1, tupdesc))
+ {
+ int replCol = i + 1;
+ Datum replValue = 0;
+ bool replIsnull = true;
+
+ tuple = heap_modify_tuple_by_cols(tuple, tupdesc, 1, &replCol, &replValue, &replIsnull);
+ }
+ }
+ }
+
+ return tuple;
+}