aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/executor/execUtils.c33
-rw-r--r--src/backend/executor/nodeModifyTable.c162
-rw-r--r--src/backend/optimizer/util/inherit.c97
-rw-r--r--src/backend/optimizer/util/plancat.c56
-rw-r--r--src/backend/replication/logical/worker.c3
-rw-r--r--src/backend/rewrite/rewriteHandler.c40
-rw-r--r--src/include/nodes/execnodes.h16
-rw-r--r--src/include/nodes/parsenodes.h9
-rw-r--r--src/include/optimizer/inherit.h2
-rw-r--r--src/include/optimizer/plancat.h3
-rw-r--r--src/include/rewrite/rewriteHandler.h3
-rw-r--r--src/test/regress/expected/generated.out19
-rw-r--r--src/test/regress/sql/generated.sql9
13 files changed, 327 insertions, 125 deletions
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 358e5828831..122de068022 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -132,6 +132,8 @@ CreateExecutorState(void)
estate->es_insert_pending_result_relations = NIL;
estate->es_insert_pending_modifytables = NIL;
+ estate->es_resultrelinfo_extra = NIL;
+
estate->es_param_list_info = NULL;
estate->es_param_exec_vals = NULL;
@@ -1323,26 +1325,25 @@ ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
Bitmapset *
ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
{
- /* see ExecGetInsertedCols() */
- if (relinfo->ri_RangeTableIndex != 0)
- {
- RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+ Relation rel = relinfo->ri_RelationDesc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
- return rte->extraUpdatedCols;
- }
- else if (relinfo->ri_RootResultRelInfo)
+ if (tupdesc->constr && tupdesc->constr->has_generated_stored)
{
- ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
- RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+ ListCell *lc;
- if (relinfo->ri_RootToPartitionMap != NULL)
- return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
- rte->extraUpdatedCols);
- else
- return rte->extraUpdatedCols;
+ /* Assert that ExecInitStoredGenerated has been called. */
+ Assert(relinfo->ri_GeneratedExprs != NULL);
+ foreach(lc, estate->es_resultrelinfo_extra)
+ {
+ ResultRelInfoExtra *rextra = (ResultRelInfoExtra *) lfirst(lc);
+
+ if (rextra->rinfo == relinfo)
+ return rextra->ri_extraUpdatedCols;
+ }
+ Assert(false); /* shouldn't get here */
}
- else
- return NULL;
+ return NULL;
}
/* Return columns being updated, including generated columns */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8794528d6af..4c49edef250 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -54,6 +54,7 @@
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
#include "rewrite/rewriteHandler.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
@@ -353,69 +354,128 @@ ExecCheckTIDVisible(EState *estate,
}
/*
- * Compute stored generated columns for a tuple
+ * Initialize to compute stored generated columns for a tuple
+ *
+ * This fills the resultRelInfo's ri_GeneratedExprs field and makes an
+ * associated ResultRelInfoExtra struct to hold ri_extraUpdatedCols.
+ * (Currently, ri_extraUpdatedCols is consulted only in UPDATE, but we might
+ * as well fill it for INSERT too.)
*/
-void
-ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
- EState *estate, TupleTableSlot *slot,
- CmdType cmdtype)
+static void
+ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
+ EState *estate,
+ CmdType cmdtype)
{
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
int natts = tupdesc->natts;
+ Bitmapset *updatedCols;
+ ResultRelInfoExtra *rextra;
MemoryContext oldContext;
- Datum *values;
- bool *nulls;
- Assert(tupdesc->constr && tupdesc->constr->has_generated_stored);
+ /* Don't call twice */
+ Assert(resultRelInfo->ri_GeneratedExprs == NULL);
+
+ /* Nothing to do if no generated columns */
+ if (!(tupdesc->constr && tupdesc->constr->has_generated_stored))
+ return;
/*
- * If first time through for this result relation, build expression
- * nodetrees for rel's stored generation expressions. Keep them in the
- * per-query memory context so they'll survive throughout the query.
+ * In an UPDATE, we can skip computing any generated columns that do not
+ * depend on any UPDATE target column. But if there is a BEFORE ROW
+ * UPDATE trigger, we cannot skip because the trigger might change more
+ * columns.
*/
- if (resultRelInfo->ri_GeneratedExprs == NULL)
- {
- oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+ if (cmdtype == CMD_UPDATE &&
+ !(rel->trigdesc && rel->trigdesc->trig_update_before_row))
+ updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
+ else
+ updatedCols = NULL;
- resultRelInfo->ri_GeneratedExprs =
- (ExprState **) palloc(natts * sizeof(ExprState *));
- resultRelInfo->ri_NumGeneratedNeeded = 0;
+ /*
+ * Make sure these data structures are built in the per-query memory
+ * context so they'll survive throughout the query.
+ */
+ oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ resultRelInfo->ri_GeneratedExprs =
+ (ExprState **) palloc0(natts * sizeof(ExprState *));
+ resultRelInfo->ri_NumGeneratedNeeded = 0;
+
+ rextra = palloc_object(ResultRelInfoExtra);
+ rextra->rinfo = resultRelInfo;
+ rextra->ri_extraUpdatedCols = NULL;
+ estate->es_resultrelinfo_extra = lappend(estate->es_resultrelinfo_extra,
+ rextra);
- for (int i = 0; i < natts; i++)
+ for (int i = 0; i < natts; i++)
+ {
+ if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
{
- if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
- {
- Expr *expr;
+ Expr *expr;
- /*
- * If it's an update and the current column was not marked as
- * being updated, then we can skip the computation. But if
- * there is a BEFORE ROW UPDATE trigger, we cannot skip
- * because the trigger might affect additional columns.
- */
- if (cmdtype == CMD_UPDATE &&
- !(rel->trigdesc && rel->trigdesc->trig_update_before_row) &&
- !bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
- ExecGetExtraUpdatedCols(resultRelInfo, estate)))
- {
- resultRelInfo->ri_GeneratedExprs[i] = NULL;
- continue;
- }
+ /* Fetch the GENERATED AS expression tree */
+ expr = (Expr *) build_column_default(rel, i + 1);
+ if (expr == NULL)
+ elog(ERROR, "no generation expression found for column number %d of table \"%s\"",
+ i + 1, RelationGetRelationName(rel));
+
+ /*
+ * If it's an update with a known set of update target columns,
+ * see if we can skip the computation.
+ */
+ if (updatedCols)
+ {
+ Bitmapset *attrs_used = NULL;
- expr = (Expr *) build_column_default(rel, i + 1);
- if (expr == NULL)
- elog(ERROR, "no generation expression found for column number %d of table \"%s\"",
- i + 1, RelationGetRelationName(rel));
+ pull_varattnos((Node *) expr, 1, &attrs_used);
- resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
- resultRelInfo->ri_NumGeneratedNeeded++;
+ if (!bms_overlap(updatedCols, attrs_used))
+ continue; /* need not update this column */
}
- }
- MemoryContextSwitchTo(oldContext);
+ /* No luck, so prepare the expression for execution */
+ resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
+ resultRelInfo->ri_NumGeneratedNeeded++;
+
+ /* And mark this column in rextra->ri_extraUpdatedCols */
+ rextra->ri_extraUpdatedCols =
+ bms_add_member(rextra->ri_extraUpdatedCols,
+ i + 1 - FirstLowInvalidHeapAttributeNumber);
+ }
}
+ MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Compute stored generated columns for a tuple
+ */
+void
+ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
+ EState *estate, TupleTableSlot *slot,
+ CmdType cmdtype)
+{
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int natts = tupdesc->natts;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ MemoryContext oldContext;
+ Datum *values;
+ bool *nulls;
+
+ /* We should not be called unless this is true */
+ Assert(tupdesc->constr && tupdesc->constr->has_generated_stored);
+
+ /*
+ * For relations named directly in the query, ExecInitStoredGenerated
+ * should have been called already; but this might not have happened yet
+ * for a partition child rel. Also, it's convenient for outside callers
+ * to not have to call ExecInitStoredGenerated explicitly.
+ */
+ if (resultRelInfo->ri_GeneratedExprs == NULL)
+ ExecInitStoredGenerated(resultRelInfo, estate, cmdtype);
+
/*
* If no generated columns have been affected by this change, then skip
* the rest.
@@ -435,14 +495,13 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
{
Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
- if (attr->attgenerated == ATTRIBUTE_GENERATED_STORED &&
- resultRelInfo->ri_GeneratedExprs[i])
+ if (resultRelInfo->ri_GeneratedExprs[i])
{
- ExprContext *econtext;
Datum val;
bool isnull;
- econtext = GetPerTupleExprContext(estate);
+ Assert(attr->attgenerated == ATTRIBUTE_GENERATED_STORED);
+
econtext->ecxt_scantuple = slot;
val = ExecEvalExpr(resultRelInfo->ri_GeneratedExprs[i], econtext, &isnull);
@@ -4088,6 +4147,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
elog(ERROR, "could not find junk wholerow column");
}
}
+
+ /*
+ * For INSERT and UPDATE, prepare to evaluate any generated columns.
+ * We must do this now, even if we never insert or update any rows,
+ * because we have to fill resultRelInfo->ri_extraUpdatedCols for
+ * possible use by the trigger machinery.
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecInitStoredGenerated(resultRelInfo, estate, operation);
}
/*
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 6dbffe121a5..3c11f5db5ad 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -25,6 +25,7 @@
#include "optimizer/inherit.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/prep.h"
@@ -47,6 +48,10 @@ static void expand_single_inheritance_child(PlannerInfo *root,
Index *childRTindex_p);
static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
List *translated_vars);
+static Bitmapset *translate_col_privs_multilevel(PlannerInfo *root,
+ RelOptInfo *rel,
+ RelOptInfo *parent_rel,
+ Bitmapset *parent_cols);
static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte, Index rti);
@@ -333,11 +338,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
root->partColsUpdated =
has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
- /*
- * There shouldn't be any generated columns in the partition key.
- */
- Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
-
/* Nothing further to do here if there are no partitions. */
if (partdesc->nparts == 0)
return;
@@ -556,15 +556,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
appinfo->translated_vars);
childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
appinfo->translated_vars);
- childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
- appinfo->translated_vars);
}
else
{
childrte->selectedCols = bms_copy(parentrte->selectedCols);
childrte->insertedCols = bms_copy(parentrte->insertedCols);
childrte->updatedCols = bms_copy(parentrte->updatedCols);
- childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
}
/*
@@ -649,6 +646,52 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
}
/*
+ * get_rel_all_updated_cols
+ * Returns the set of columns of a given "simple" relation that are
+ * updated by this query.
+ */
+Bitmapset *
+get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel)
+{
+ Index relid;
+ RangeTblEntry *rte;
+ Bitmapset *updatedCols,
+ *extraUpdatedCols;
+
+ Assert(root->parse->commandType == CMD_UPDATE);
+ Assert(IS_SIMPLE_REL(rel));
+
+ /*
+ * We obtain updatedCols for the query's result relation. Then, if
+ * necessary, we map it to the column numbers of the relation for which
+ * they were requested.
+ */
+ relid = root->parse->resultRelation;
+ rte = planner_rt_fetch(relid, root);
+
+ updatedCols = rte->updatedCols;
+
+ if (rel->relid != relid)
+ {
+ RelOptInfo *top_parent_rel = find_base_rel(root, relid);
+
+ Assert(IS_OTHER_REL(rel));
+
+ updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+ updatedCols);
+ }
+
+ /*
+ * Now we must check to see if there are any generated columns that depend
+ * on the updatedCols, and add them to the result.
+ */
+ extraUpdatedCols = get_dependent_generated_columns(root, rel->relid,
+ updatedCols);
+
+ return bms_union(updatedCols, extraUpdatedCols);
+}
+
+/*
* translate_col_privs
* Translate a bitmapset representing per-column privileges from the
* parent rel's attribute numbering to the child's.
@@ -701,6 +744,44 @@ translate_col_privs(const Bitmapset *parent_privs,
}
/*
+ * translate_col_privs_multilevel
+ * Recursively translates the column numbers contained in 'parent_cols'
+ * to the column numbers of a descendant relation given by 'rel'
+ *
+ * Note that because this is based on translate_col_privs, it will expand
+ * a whole-row reference into all inherited columns. This is not an issue
+ * for current usages, but beware.
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+ RelOptInfo *parent_rel,
+ Bitmapset *parent_cols)
+{
+ AppendRelInfo *appinfo;
+
+ /* Fast path for easy case. */
+ if (parent_cols == NULL)
+ return NULL;
+
+ Assert(root->append_rel_array != NULL);
+ appinfo = root->append_rel_array[rel->relid];
+ Assert(appinfo != NULL);
+
+ /* Recurse if immediate parent is not the top parent. */
+ if (appinfo->parent_relid != parent_rel->relid)
+ {
+ RelOptInfo *next_parent = find_base_rel(root, appinfo->parent_relid);
+
+ parent_cols = translate_col_privs_multilevel(root, next_parent,
+ parent_rel,
+ parent_cols);
+ }
+
+ /* Now translate for this child. */
+ return translate_col_privs(parent_cols, appinfo->translated_vars);
+}
+
+/*
* expand_appendrel_subquery
* Add "other rel" RelOptInfos for the children of an appendrel baserel
*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c328a2f9129..419f2ac55fa 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -2198,6 +2198,11 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
return result;
}
+/*
+ * has_stored_generated_columns
+ *
+ * Does table identified by RTI have any STORED GENERATED columns?
+ */
bool
has_stored_generated_columns(PlannerInfo *root, Index rti)
{
@@ -2218,6 +2223,57 @@ has_stored_generated_columns(PlannerInfo *root, Index rti)
}
/*
+ * get_dependent_generated_columns
+ *
+ * Get the column numbers of any STORED GENERATED columns of the relation
+ * that depend on any column listed in target_cols. Both the input and
+ * result bitmapsets contain column numbers offset by
+ * FirstLowInvalidHeapAttributeNumber.
+ */
+Bitmapset *
+get_dependent_generated_columns(PlannerInfo *root, Index rti,
+ Bitmapset *target_cols)
+{
+ Bitmapset *dependentCols = NULL;
+ RangeTblEntry *rte = planner_rt_fetch(rti, root);
+ Relation relation;
+ TupleDesc tupdesc;
+ TupleConstr *constr;
+
+ /* Assume we already have adequate lock */
+ relation = table_open(rte->relid, NoLock);
+
+ tupdesc = RelationGetDescr(relation);
+ constr = tupdesc->constr;
+
+ if (constr && constr->has_generated_stored)
+ {
+ for (int i = 0; i < constr->num_defval; i++)
+ {
+ AttrDefault *defval = &constr->defval[i];
+ Node *expr;
+ Bitmapset *attrs_used = NULL;
+
+ /* skip if not generated column */
+ if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+ continue;
+
+ /* identify columns this generated column depends on */
+ expr = stringToNode(defval->adbin);
+ pull_varattnos(expr, 1, &attrs_used);
+
+ if (bms_overlap(target_cols, attrs_used))
+ dependentCols = bms_add_member(dependentCols,
+ defval->adnum - FirstLowInvalidHeapAttributeNumber);
+ }
+ }
+
+ table_close(relation, NoLock);
+
+ return dependentCols;
+}
+
+/*
* set_relation_partition_info
*
* Set partitioning scheme and related information for a partitioned table.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 1825daf903e..e2e3da05910 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1851,9 +1851,6 @@ apply_handle_update(StringInfo s)
}
}
- /* Also populate extraUpdatedCols, in case we have generated columns */
- fill_extraUpdatedCols(target_rte, rel->localrel);
-
/* Build the search tuple. */
oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
slot_store_data(remoteslot, rel,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 45bcc84cf02..dcfcdd0dd43 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1627,43 +1627,6 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
-{
- TupleDesc tupdesc = RelationGetDescr(target_relation);
- TupleConstr *constr = tupdesc->constr;
-
- target_rte->extraUpdatedCols = NULL;
-
- if (constr && constr->has_generated_stored)
- {
- for (int i = 0; i < constr->num_defval; i++)
- {
- AttrDefault *defval = &constr->defval[i];
- Node *expr;
- Bitmapset *attrs_used = NULL;
-
- /* skip if not generated column */
- if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
- continue;
-
- /* identify columns this generated column depends on */
- expr = stringToNode(defval->adbin);
- pull_varattnos(expr, 1, &attrs_used);
-
- if (bms_overlap(target_rte->updatedCols, attrs_used))
- target_rte->extraUpdatedCols =
- bms_add_member(target_rte->extraUpdatedCols,
- defval->adnum - FirstLowInvalidHeapAttributeNumber);
- }
- }
-}
-
-
-/*
* matchLocks -
* match the list of locks and returns the matching rules
*/
@@ -3831,9 +3794,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
parsetree->override,
rt_entry_relation,
NULL, 0, NULL);
-
- /* Also populate extraUpdatedCols (for generated columns) */
- fill_extraUpdatedCols(rt_entry, rt_entry_relation);
}
else if (event == CMD_MERGE)
{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 580e99242be..f34d06eff4f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -558,6 +558,19 @@ typedef struct ResultRelInfo
List *ri_ancestorResultRels;
} ResultRelInfo;
+/*
+ * To avoid an ABI-breaking change in the size of ResultRelInfo in back
+ * branches, we create one of these for each result relation for which we've
+ * computed extraUpdatedCols, and store it in EState.es_resultrelinfo_extra.
+ */
+typedef struct ResultRelInfoExtra
+{
+ ResultRelInfo *rinfo; /* owning ResultRelInfo */
+
+ /* For INSERT/UPDATE, attnums of generated columns to be computed */
+ Bitmapset *ri_extraUpdatedCols;
+} ResultRelInfoExtra;
+
/* ----------------
* AsyncRequest
*
@@ -684,6 +697,9 @@ typedef struct EState
*/
List *es_insert_pending_result_relations;
List *es_insert_pending_modifytables;
+
+ /* List of ResultRelInfoExtra structs (see above) */
+ List *es_resultrelinfo_extra;
} EState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1e7280340b4..6944362c7ac 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -977,14 +977,7 @@ typedef struct PartitionCmd
* which triggers to fire and in FDWs to know which changed columns they
* need to ship off.
*
- * Generated columns that are caused to be updated by an update to a base
- * column are listed in extraUpdatedCols. This is not considered for
- * permission checking, but it is useful in those places that want to know
- * the full set of columns being updated as opposed to only the ones the
- * user explicitly mentioned in the query. (There is currently no need for
- * an extraInsertedCols, but it could exist.) Note that extraUpdatedCols
- * is populated during query rewrite, NOT in the parser, since generated
- * columns could be added after a rule has been parsed and stored.
+ * extraUpdatedCols is no longer used or maintained; it's always empty.
*
* securityQuals is a list of security barrier quals (boolean expressions),
* to be tested in the listed order before returning a row from the
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d73722..8ebd42b757a 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -20,6 +20,8 @@
extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte, Index rti);
+extern Bitmapset *get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel);
+
extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
RelOptInfo *childrel, RangeTblEntry *childRTE,
AppendRelInfo *appinfo);
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index ad29b5d107d..b0c06ca14e1 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -74,4 +74,7 @@ extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern Bitmapset *get_dependent_generated_columns(PlannerInfo *root, Index rti,
+ Bitmapset *target_cols);
+
#endif /* PLANCAT_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109afc..b4f96f298b6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,9 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
extern Node *build_column_default(Relation rel, int attrno);
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
- Relation target_relation);
-
extern Query *get_view_query(Relation view);
extern const char *view_query_is_auto_updatable(Query *viewquery,
bool check_cols);
diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out
index bb4190340e7..1db5f9ed47b 100644
--- a/src/test/regress/expected/generated.out
+++ b/src/test/regress/expected/generated.out
@@ -337,6 +337,25 @@ CREATE TABLE gtest1_2 () INHERITS (gtest0, gtesty); -- error
NOTICE: merging multiple inherited definitions of column "b"
ERROR: inherited column "b" has a generation conflict
DROP TABLE gtesty;
+-- test correct handling of GENERATED column that's only in child
+CREATE TABLE gtestp (f1 int);
+CREATE TABLE gtestc (f2 int GENERATED ALWAYS AS (f1+1) STORED) INHERITS(gtestp);
+INSERT INTO gtestc values(42);
+TABLE gtestc;
+ f1 | f2
+----+----
+ 42 | 43
+(1 row)
+
+UPDATE gtestp SET f1 = f1 * 10;
+TABLE gtestc;
+ f1 | f2
+-----+-----
+ 420 | 421
+(1 row)
+
+DROP TABLE gtestp CASCADE;
+NOTICE: drop cascades to table gtestc
-- test stored update
CREATE TABLE gtest3 (a int, b int GENERATED ALWAYS AS (a * 3) STORED);
INSERT INTO gtest3 (a) VALUES (1), (2), (3), (NULL);
diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql
index 378297e6ea6..39eec40bce9 100644
--- a/src/test/regress/sql/generated.sql
+++ b/src/test/regress/sql/generated.sql
@@ -149,6 +149,15 @@ CREATE TABLE gtesty (x int, b int DEFAULT 55);
CREATE TABLE gtest1_2 () INHERITS (gtest0, gtesty); -- error
DROP TABLE gtesty;
+-- test correct handling of GENERATED column that's only in child
+CREATE TABLE gtestp (f1 int);
+CREATE TABLE gtestc (f2 int GENERATED ALWAYS AS (f1+1) STORED) INHERITS(gtestp);
+INSERT INTO gtestc values(42);
+TABLE gtestc;
+UPDATE gtestp SET f1 = f1 * 10;
+TABLE gtestc;
+DROP TABLE gtestp CASCADE;
+
-- test stored update
CREATE TABLE gtest3 (a int, b int GENERATED ALWAYS AS (a * 3) STORED);
INSERT INTO gtest3 (a) VALUES (1), (2), (3), (NULL);