aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeModifyTable.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2023-01-05 14:12:17 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2023-01-05 14:12:17 -0500
commit3706cc97aa36bb65fd82dbfc79ca809033bcad1f (patch)
tree8abc1f1e246198923bcef97ffb7fcc66020cd4ec /src/backend/executor/nodeModifyTable.c
parentaa26980ca0813200c25b06e3ad0f54d449cce8b2 (diff)
downloadpostgresql-3706cc97aa36bb65fd82dbfc79ca809033bcad1f.tar.gz
postgresql-3706cc97aa36bb65fd82dbfc79ca809033bcad1f.zip
Fix calculation of which GENERATED columns need to be updated.
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
Diffstat (limited to 'src/backend/executor/nodeModifyTable.c')
-rw-r--r--src/backend/executor/nodeModifyTable.c162
1 files changed, 115 insertions, 47 deletions
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);
}
/*