Fix calculation of which GENERATED columns need to be updated.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 5 Jan 2023 19:12:17 +0000 (14:12 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 5 Jan 2023 19:12:17 +0000 (14:12 -0500)
We were identifying the updatable generated columns of inheritance
children by transposing the calculation made for their parent.
However, there's nothing that says a traditional-inheritance child
can't have generated columns that aren't there in its parent, or that
have different dependencies than are in the parent's expression.
(At present it seems that we don't enforce that for partitioning
either, which is likely wrong to some degree or other; but the case
clearly needs to be handled with traditional inheritance.)

Hence, drop the very-klugy-anyway "extraUpdatedCols" RTE field
in favor of identifying which generated columns depend on updated
columns during executor startup.  In HEAD we can remove
extraUpdatedCols altogether; in back branches, it's still there but
always empty.  Another difference between the HEAD and back-branch
versions of this patch is that in HEAD we can add the new bitmap field
to ResultRelInfo, but that would cause an ABI break in back branches.
Like 4b3e37993, add a List field at the end of struct EState instead.

Back-patch to v13.  The bogus calculation is also being made in v12,
but it doesn't have the same visible effect because we don't use it
to decide which generated columns to recalculate; as a consequence of
which the patch doesn't apply easily.  I think that there might still
be a demonstrable bug associated with trigger firing conditions, but
that's such a weird corner-case usage that I'm content to leave it
unfixed in v12.

Amit Langote and Tom Lane

Discussion: https://postgr.es/m/CA+HiwqFshLKNvQUd1DgwJ-7tsTp=dwv7KZqXC4j2wYBV1aCDUA@mail.gmail.com
Discussion: https://postgr.es/m/2793383.1672944799@sss.pgh.pa.us

15 files changed:
src/backend/executor/execMain.c
src/backend/executor/execUtils.c
src/backend/executor/nodeModifyTable.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/util/inherit.c
src/backend/optimizer/util/plancat.c
src/backend/replication/logical/worker.c
src/backend/rewrite/rewriteHandler.c
src/include/nodes/execnodes.h
src/include/nodes/parsenodes.h
src/include/optimizer/plancat.h
src/include/rewrite/rewriteHandler.h
src/test/regress/expected/generated.out
src/test/regress/sql/generated.sql

index 55a090b1989eebb4126fba89db3bf0cff0ea3702..a5115b9c1f7b8197ba95544c8b1e420ebefd6698 100644 (file)
@@ -1225,6 +1225,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 
    /* The following fields are set later if needed */
    resultRelInfo->ri_RowIdAttNo = 0;
+   resultRelInfo->ri_extraUpdatedCols = NULL;
    resultRelInfo->ri_projectNew = NULL;
    resultRelInfo->ri_newTupleSlot = NULL;
    resultRelInfo->ri_oldTupleSlot = NULL;
index 8bcf4784eb9a049d686e2ea8bdfd1ecc2bea3049..8c82c7167527554ca61ab07ac282b89e0ad35c3c 100644 (file)
@@ -1342,25 +1342,16 @@ ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-   if (relinfo->ri_RangeTableIndex != 0)
-   {
-       RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+#ifdef USE_ASSERT_CHECKING
+   /* Verify that ExecInitStoredGenerated has been called if needed. */
+   Relation    rel = relinfo->ri_RelationDesc;
+   TupleDesc   tupdesc = RelationGetDescr(rel);
 
-       return rte->extraUpdatedCols;
-   }
-   else if (relinfo->ri_RootResultRelInfo)
-   {
-       ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-       RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
-       TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
+   if (tupdesc->constr && tupdesc->constr->has_generated_stored)
+       Assert(relinfo->ri_GeneratedExprs != NULL);
+#endif
 
-       if (map != NULL)
-           return execute_attr_map_cols(map->attrMap, rte->extraUpdatedCols);
-       else
-           return rte->extraUpdatedCols;
-   }
-   else
-       return NULL;
+   return relinfo->ri_extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
index 56398c399c9d2705e6fb92cdd08ef881e96c7f1b..687a5422eabb297121f6ee1def4ff2a91c74a383 100644 (file)
@@ -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,120 @@ 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 and ri_extraUpdatedCols
+ * fields.  (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;
    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);
 
-       for (int i = 0; i < natts; i++)
+   resultRelInfo->ri_GeneratedExprs =
+       (ExprState **) palloc0(natts * sizeof(ExprState *));
+   resultRelInfo->ri_NumGeneratedNeeded = 0;
+
+   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));
 
-               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;
+
+               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 resultRelInfo->ri_extraUpdatedCols */
+           resultRelInfo->ri_extraUpdatedCols =
+               bms_add_member(resultRelInfo->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 +487,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 +4139,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);
    }
 
    /*
index 0cb235bfda80b15b481d79557ea47c0ff5c11d0f..69324d5a9aa5f9b295e4da28d50f3e5c49f370e5 100644 (file)
@@ -561,7 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
    WRITE_BOOL_FIELD(lateral);
    WRITE_BOOL_FIELD(inh);
    WRITE_BOOL_FIELD(inFromCl);
-   WRITE_BITMAPSET_FIELD(extraUpdatedCols);
    WRITE_NODE_FIELD(securityQuals);
 }
 
index c284b96a544eaa9f1b164fbc11ef0b62897e3b50..30cd7a0da6bbfb93f1bdef9ae7c76698a9927859 100644 (file)
@@ -537,7 +537,6 @@ _readRangeTblEntry(void)
    READ_BOOL_FIELD(lateral);
    READ_BOOL_FIELD(inh);
    READ_BOOL_FIELD(inFromCl);
-   READ_BITMAPSET_FIELD(extraUpdatedCols);
    READ_NODE_FIELD(securityQuals);
 
    READ_DONE();
index 6c93c2247747245911a2abbee478abeff9b49045..bf3ea26cf4b2c80833a90b8dac94b6e961302aee 100644 (file)
@@ -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"
@@ -51,7 +52,7 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
                                      List *translated_vars);
 static Bitmapset *translate_col_privs_multilevel(PlannerInfo *root,
                                                 RelOptInfo *rel,
-                                                RelOptInfo *top_parent_rel,
+                                                RelOptInfo *parent_rel,
                                                 Bitmapset *parent_cols);
 static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
                                      RangeTblEntry *rte, Index rti);
@@ -345,11 +346,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
        root->partColsUpdated =
            has_partition_attrs(parentrel, parent_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;
@@ -566,13 +562,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
    childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
                                                 child_colnames);
 
-   /* Translate the bitmapset of generated columns being updated. */
-   if (childOID != parentOID)
-       childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-                                                        appinfo->translated_vars);
-   else
-       childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
    /*
     * Store the RTE and appinfo in the respective PlannerInfo arrays, which
     * the caller must already have allocated space for.
@@ -672,21 +661,16 @@ get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel)
    Assert(IS_SIMPLE_REL(rel));
 
    /*
-    * We obtain updatedCols and extraUpdatedCols for the query's result
-    * relation.  Then, if necessary, we map it to the column numbers of the
-    * relation for which they were requested.
+    * 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);
    perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
 
    updatedCols = perminfo->updatedCols;
-   extraUpdatedCols = rte->extraUpdatedCols;
 
-   /*
-    * For "other" rels, we must look up the root parent relation mentioned in
-    * the query, and translate the column numbers.
-    */
    if (rel->relid != relid)
    {
        RelOptInfo *top_parent_rel = find_base_rel(root, relid);
@@ -695,10 +679,15 @@ get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel)
 
        updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
                                                     updatedCols);
-       extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
-                                                         extraUpdatedCols);
    }
 
+   /*
+    * 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);
 }
 
@@ -754,6 +743,45 @@ translate_col_privs(const Bitmapset *parent_privs,
    return child_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;
+
+   /* Recurse if immediate parent is not the top parent. */
+   if (rel->parent != parent_rel)
+   {
+       if (rel->parent)
+           parent_cols = translate_col_privs_multilevel(root, rel->parent,
+                                                        parent_rel,
+                                                        parent_cols);
+       else
+           elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+   }
+
+   /* Now translate for this child. */
+   Assert(root->append_rel_array != NULL);
+   appinfo = root->append_rel_array[rel->relid];
+   Assert(appinfo != NULL);
+
+   return translate_col_privs(parent_cols, appinfo->translated_vars);
+}
+
 /*
  * expand_appendrel_subquery
  *     Add "other rel" RelOptInfos for the children of an appendrel baserel
@@ -920,36 +948,3 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
    return true;
 }
-
-/*
- * translate_col_privs_multilevel
- *     Recursively translates the column numbers contained in 'parent_cols'
- *     to the columns numbers of a descendent relation given by 'rel'
- */
-static Bitmapset *
-translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
-                              RelOptInfo *top_parent_rel,
-                              Bitmapset *parent_cols)
-{
-   AppendRelInfo *appinfo;
-
-   if (parent_cols == NULL)
-       return NULL;
-
-   /* Recurse if immediate parent is not the top parent. */
-   if (rel->parent != top_parent_rel)
-   {
-       if (rel->parent)
-           parent_cols = translate_col_privs_multilevel(root, rel->parent,
-                                                        top_parent_rel,
-                                                        parent_cols);
-       else
-           elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
-   }
-
-   Assert(root->append_rel_array != NULL);
-   appinfo = root->append_rel_array[rel->relid];
-   Assert(appinfo != NULL);
-
-   return translate_col_privs(parent_cols, appinfo->translated_vars);
-}
index c24bdae717f17f7cb8226833ac578f19842bd9f7..9f158f2421b0f7be3b6da784b098298d557bc410 100644 (file)
@@ -2199,6 +2199,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)
    return result;
 }
 
+/*
+ * 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
  *
index 446f84fa974afd0d3a5595b8cdda66df30cf7dfc..f8e8cf71eb86dd847a4e12c2e9da1f26f8bd3ead 100644 (file)
@@ -1815,7 +1815,6 @@ apply_handle_update(StringInfo s)
    LogicalRepTupleData newtup;
    bool        has_oldtup;
    TupleTableSlot *remoteslot;
-   RangeTblEntry *target_rte;
    RTEPermissionInfo *target_perminfo;
    MemoryContext oldctx;
 
@@ -1864,7 +1863,6 @@ apply_handle_update(StringInfo s)
     * information.  But it would for example exclude columns that only exist
     * on the subscriber, since we are not touching those.
     */
-   target_rte = list_nth(estate->es_range_table, 0);
    target_perminfo = list_nth(estate->es_rteperminfos, 0);
    for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
    {
@@ -1881,9 +1879,6 @@ apply_handle_update(StringInfo s)
        }
    }
 
-   /* Also populate extraUpdatedCols, in case we have generated columns */
-   fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
-
    /* Build the search tuple. */
    oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
    slot_store_data(remoteslot, rel,
index ca8b543bc184be4af701b96d1ad5c8d826608c9f..1960dad7013bc29846ffe7d18e6af1e76ead7b99 100644 (file)
@@ -1633,46 +1633,6 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-                     RTEPermissionInfo *target_perminfo,
-                     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_perminfo->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
@@ -3738,7 +3698,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
    {
        int         result_relation;
        RangeTblEntry *rt_entry;
-       RTEPermissionInfo *rt_perminfo;
        Relation    rt_entry_relation;
        List       *locks;
        int         product_orig_rt_length;
@@ -3751,7 +3710,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
        Assert(result_relation != 0);
        rt_entry = rt_fetch(result_relation, parsetree->rtable);
        Assert(rt_entry->rtekind == RTE_RELATION);
-       rt_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, rt_entry);
 
        /*
         * We can use NoLock here since either the parser or
@@ -3843,9 +3801,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_perminfo, rt_entry_relation);
        }
        else if (event == CMD_MERGE)
        {
index 2cd0a4f472f8d7a499c882c267286d3b662499e7..20f4c8b35f31b8977c0ed069b94f94734f9a89b7 100644 (file)
@@ -462,6 +462,9 @@ typedef struct ResultRelInfo
     */
    AttrNumber  ri_RowIdAttNo;
 
+   /* For INSERT/UPDATE, attnums of generated columns to be computed */
+   Bitmapset  *ri_extraUpdatedCols;
+
    /* Projection to generate new tuple in an INSERT/UPDATE */
    ProjectionInfo *ri_projectNew;
    /* Slot to hold that tuple */
index 6300945734c3a39bbfa3ce4e267cf35c45c15632..cfeca96d532cc8ff1ea3b5a8143ec0d6d4d97706 100644 (file)
@@ -1153,7 +1153,6 @@ typedef struct RangeTblEntry
    bool        lateral;        /* subquery, function, or values is LATERAL? */
    bool        inh;            /* inheritance requested? */
    bool        inFromCl;       /* present in FROM clause? */
-   Bitmapset  *extraUpdatedCols;   /* generated columns being updated */
    List       *securityQuals;  /* security barrier quals to apply, if any */
 } RangeTblEntry;
 
@@ -1189,15 +1188,6 @@ typedef struct RangeTblEntry
  * updatedCols is also used in some other places, for example, to determine
  * 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.
  */
 typedef struct RTEPermissionInfo
 {
index 21edac04ea56bbeadb97927ae21fca462c3c6775..eb1c3ccc4bfb2d4ed08db2541490310b157fd9b7 100644 (file)
@@ -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 */
index 62bca7cfdf82b2337f7a95cffd4ac2e9d55d5553..b71e20b087c066ac93d2b08f9e3a3c2dad2d3b49 100644 (file)
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-                                 RTEPermissionInfo *target_perminfo,
-                                 Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
                                                bool check_cols);
index bb4190340e76b52a05c586b003461def60557736..1db5f9ed47b1c0da31b8b1a53e347ecc8b4f53c0 100644 (file)
@@ -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);
index 378297e6ea675851f79b263ecc51039544d1e318..39eec40bce94b23f2c2344393e5b0c2eecae5334 100644 (file)
@@ -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);