aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeModifyTable.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2021-04-06 15:56:55 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2021-04-06 15:57:11 -0400
commitc5b7ba4e67aeb5d6f824b74f94114d99ed6e42b7 (patch)
tree49af7ec3889470f92b7eab567ad990028bae6177 /src/backend/executor/nodeModifyTable.c
parenta3740c48eb2f91663c7c06c948dfcfb6493d2588 (diff)
downloadpostgresql-c5b7ba4e67aeb5d6f824b74f94114d99ed6e42b7.tar.gz
postgresql-c5b7ba4e67aeb5d6f824b74f94114d99ed6e42b7.zip
Postpone some stuff out of ExecInitModifyTable.
Arrange to do some things on-demand, rather than immediately during executor startup, because there's a fair chance of never having to do them at all: * Don't open result relations' indexes until needed. * Don't initialize partition tuple routing, nor the child-to-root tuple conversion map, until needed. This wins in UPDATEs on partitioned tables when only some of the partitions will actually receive updates; with larger partition counts the savings is quite noticeable. Also, we can remove some sketchy heuristics in ExecInitModifyTable about whether to set up tuple routing. Also, remove execPartition.c's private hash table tracking which partitions were already opened by the ModifyTable node. Instead use the hash added to ModifyTable itself by commit 86dc90056. To allow lazy computation of the conversion maps, we now set ri_RootResultRelInfo in all child ResultRelInfos. We formerly set it only in some, not terribly well-defined, cases. This has user-visible side effects in that now more error messages refer to the root relation instead of some partition (and provide error data in the root's column order, too). It looks to me like this is a strict improvement in consistency, so I don't have a problem with the output changes visible in this commit. Extracted from a larger patch, which seemed to me to be too messy to push in one commit. Amit Langote, reviewed at different times by Heikki Linnakangas and myself Discussion: https://postgr.es/m/CA+HiwqG7ZruBmmih3wPsBZ4s0H2EhywrnXEduckY5Hr3fWzPWA@mail.gmail.com
Diffstat (limited to 'src/backend/executor/nodeModifyTable.c')
-rw-r--r--src/backend/executor/nodeModifyTable.c247
1 files changed, 130 insertions, 117 deletions
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index bf65785e643..249555f234b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -493,6 +493,14 @@ ExecInsert(ModifyTableState *mtstate,
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
+ * Open the table's indexes, if we have not done so already, so that we
+ * can add new index entries for the inserted tuple.
+ */
+ if (resultRelationDesc->rd_rel->relhasindex &&
+ resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
+ /*
* BEFORE ROW INSERT Triggers.
*
* Note: We fire BEFORE ROW TRIGGERS for every attempted insertion in an
@@ -1276,7 +1284,6 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
TupleTableSlot **inserted_tuple)
{
EState *estate = mtstate->ps.state;
- PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
TupleConversionMap *tupconv_map;
bool tuple_deleted;
TupleTableSlot *epqslot = NULL;
@@ -1296,13 +1303,35 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errdetail("The result tuple would appear in a different partition than the original tuple.")));
/*
- * When an UPDATE is run on a leaf partition, we will not have partition
- * tuple routing set up. In that case, fail with partition constraint
- * violation error.
+ * When an UPDATE is run directly on a leaf partition, simply fail with a
+ * partition constraint violation error.
*/
- if (proute == NULL)
+ if (resultRelInfo == mtstate->rootResultRelInfo)
ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+ MemoryContext oldcxt;
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, rootRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first be
+ * converted to the root's format, so we'll need a slot for storing
+ * such tuples.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
* We want to return rows from INSERT.
@@ -1364,7 +1393,7 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
* convert the tuple into root's tuple descriptor if needed, since
* ExecInsert() starts the search from root.
*/
- tupconv_map = resultRelInfo->ri_ChildToRootMap;
+ tupconv_map = ExecGetChildToRootMap(resultRelInfo);
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1436,6 +1465,14 @@ ExecUpdate(ModifyTableState *mtstate,
ExecMaterializeSlot(slot);
+ /*
+ * Open the table's indexes, if we have not done so already, so that we
+ * can add new index entries for the updated tuple.
+ */
+ if (resultRelationDesc->rd_rel->relhasindex &&
+ resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
/* BEFORE ROW UPDATE Triggers */
if (resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_update_before_row)
@@ -2244,38 +2281,8 @@ ExecModifyTable(PlanState *pstate)
/* If it's not the same as last time, we need to locate the rel */
if (resultoid != node->mt_lastResultOid)
- {
- if (node->mt_resultOidHash)
- {
- /* Use the pre-built hash table to locate the rel */
- MTTargetRelLookup *mtlookup;
-
- mtlookup = (MTTargetRelLookup *)
- hash_search(node->mt_resultOidHash, &resultoid,
- HASH_FIND, NULL);
- if (!mtlookup)
- elog(ERROR, "incorrect result rel OID %u", resultoid);
- node->mt_lastResultOid = resultoid;
- node->mt_lastResultIndex = mtlookup->relationIndex;
- resultRelInfo = node->resultRelInfo + mtlookup->relationIndex;
- }
- else
- {
- /* With few target rels, just do a simple search */
- int ndx;
-
- for (ndx = 0; ndx < node->mt_nrels; ndx++)
- {
- resultRelInfo = node->resultRelInfo + ndx;
- if (RelationGetRelid(resultRelInfo->ri_RelationDesc) == resultoid)
- break;
- }
- if (ndx >= node->mt_nrels)
- elog(ERROR, "incorrect result rel OID %u", resultoid);
- node->mt_lastResultOid = resultoid;
- node->mt_lastResultIndex = ndx;
- }
- }
+ resultRelInfo = ExecLookupResultRelByOid(node, resultoid,
+ false, true);
}
/*
@@ -2466,6 +2473,61 @@ ExecModifyTable(PlanState *pstate)
return NULL;
}
+/*
+ * ExecLookupResultRelByOid
+ * If the table with given OID is among the result relations to be
+ * updated by the given ModifyTable node, return its ResultRelInfo.
+ *
+ * If not found, return NULL if missing_ok, else raise error.
+ *
+ * If update_cache is true, then upon successful lookup, update the node's
+ * one-element cache. ONLY ExecModifyTable may pass true for this.
+ */
+ResultRelInfo *
+ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid,
+ bool missing_ok, bool update_cache)
+{
+ if (node->mt_resultOidHash)
+ {
+ /* Use the pre-built hash table to locate the rel */
+ MTTargetRelLookup *mtlookup;
+
+ mtlookup = (MTTargetRelLookup *)
+ hash_search(node->mt_resultOidHash, &resultoid, HASH_FIND, NULL);
+ if (mtlookup)
+ {
+ if (update_cache)
+ {
+ node->mt_lastResultOid = resultoid;
+ node->mt_lastResultIndex = mtlookup->relationIndex;
+ }
+ return node->resultRelInfo + mtlookup->relationIndex;
+ }
+ }
+ else
+ {
+ /* With few target rels, just search the ResultRelInfo array */
+ for (int ndx = 0; ndx < node->mt_nrels; ndx++)
+ {
+ ResultRelInfo *rInfo = node->resultRelInfo + ndx;
+
+ if (RelationGetRelid(rInfo->ri_RelationDesc) == resultoid)
+ {
+ if (update_cache)
+ {
+ node->mt_lastResultOid = resultoid;
+ node->mt_lastResultIndex = ndx;
+ }
+ return rInfo;
+ }
+ }
+ }
+
+ if (!missing_ok)
+ elog(ERROR, "incorrect result relation OID %u", resultoid);
+ return NULL;
+}
+
/* ----------------------------------------------------------------
* ExecInitModifyTable
* ----------------------------------------------------------------
@@ -2482,7 +2544,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ListCell *l;
int i;
Relation rel;
- bool update_tuple_routing_needed = node->partColsUpdated;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -2554,8 +2615,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Index resultRelation = lfirst_int(l);
if (resultRelInfo != mtstate->rootResultRelInfo)
+ {
ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+ /*
+ * For child result relations, store the root result relation
+ * pointer. We do so for the convenience of places that want to
+ * look at the query's original target relation but don't have the
+ * mtstate handy.
+ */
+ resultRelInfo->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
+ }
+
/* Initialize the usesFdwDirectModify flag */
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
node->fdwDirectModifyPlans);
@@ -2581,32 +2652,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
resultRelInfo = &mtstate->resultRelInfo[i];
- /*
- * If there are indices on the result relation, open them and save
- * descriptors in the result relation info, so that we can add new
- * index entries for the tuples we add/update. We need not do this
- * for a DELETE, however, since deletion doesn't affect indexes. Also,
- * inside an EvalPlanQual operation, the indexes might be open
- * already, since we share the resultrel state with the original
- * query.
- */
- if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
- operation != CMD_DELETE &&
- resultRelInfo->ri_IndexRelationDescs == NULL)
- ExecOpenIndices(resultRelInfo,
- node->onConflictAction != ONCONFLICT_NONE);
-
- /*
- * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
- * trigger itself might modify the partition-key values. So arrange
- * for tuple routing.
- */
- if (resultRelInfo->ri_TrigDesc &&
- resultRelInfo->ri_TrigDesc->trig_update_before_row &&
- operation == CMD_UPDATE)
- update_tuple_routing_needed = true;
-
- /* Also let FDWs init themselves for foreign-table result rels */
+ /* Let FDWs init themselves for foreign-table result rels */
if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
@@ -2619,52 +2665,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
i,
eflags);
}
-
- /*
- * If needed, initialize a map to convert tuples in the child format
- * to the format of the table mentioned in the query (root relation).
- * It's needed for update tuple routing, because the routing starts
- * from the root relation. It's also needed for capturing transition
- * tuples, because the transition tuple store can only store tuples in
- * the root table format.
- *
- * For INSERT, the map is only initialized for a given partition when
- * the partition itself is first initialized by ExecFindPartition().
- */
- if (update_tuple_routing_needed ||
- (mtstate->mt_transition_capture &&
- mtstate->operation != CMD_INSERT))
- resultRelInfo->ri_ChildToRootMap =
- convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc),
- RelationGetDescr(mtstate->rootResultRelInfo->ri_RelationDesc));
}
/* Get the root target relation */
rel = mtstate->rootResultRelInfo->ri_RelationDesc;
/*
- * If it's not a partitioned table after all, UPDATE tuple routing should
- * not be attempted.
- */
- if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
- update_tuple_routing_needed = false;
-
- /*
- * Build state for tuple routing if it's an INSERT or if it's an UPDATE of
- * partition key.
+ * Build state for tuple routing if it's a partitioned INSERT. An UPDATE
+ * might need this too, but only if it actually moves tuples between
+ * partitions; in that case setup is done by ExecCrossPartitionUpdate.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- (operation == CMD_INSERT || update_tuple_routing_needed))
+ operation == CMD_INSERT)
mtstate->mt_partition_tuple_routing =
- ExecSetupPartitionTupleRouting(estate, mtstate, rel);
-
- /*
- * For update row movement we'll need a dedicated slot to store the tuples
- * that have been converted from partition format to the root table
- * format.
- */
- if (update_tuple_routing_needed)
- mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
+ ExecSetupPartitionTupleRouting(estate, rel);
/*
* Initialize any WITH CHECK OPTION constraints if needed.
@@ -2743,7 +2757,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
/* Set the list of arbiter indexes if needed for ON CONFLICT */
resultRelInfo = mtstate->resultRelInfo;
if (node->onConflictAction != ONCONFLICT_NONE)
+ {
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(nrels == 1);
resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+ }
/*
* If needed, Initialize target list, projection and qual for ON CONFLICT
@@ -2755,9 +2773,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
TupleDesc relationDesc;
TupleDesc tupDesc;
- /* insert may only have one relation, inheritance is not expanded */
- Assert(nrels == 1);
-
/* already exists if created by RETURNING processing above */
if (mtstate->ps.ps_ExprContext == NULL)
ExecAssignExprContext(estate, &mtstate->ps);
@@ -3036,22 +3051,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
if (operation == CMD_INSERT)
{
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(nrels == 1);
resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nrels; i++)
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize &&
+ resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert)
{
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize &&
- resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert)
- resultRelInfo->ri_BatchSize =
- resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(resultRelInfo);
- else
- resultRelInfo->ri_BatchSize = 1;
-
+ resultRelInfo->ri_BatchSize =
+ resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(resultRelInfo);
Assert(resultRelInfo->ri_BatchSize >= 1);
-
- resultRelInfo++;
}
+ else
+ resultRelInfo->ri_BatchSize = 1;
}
/*