ModifyTable overheads in generic plans
Hi,
I would like to discuss a refactoring patch that builds on top of the
patches at [1]https://commitfest.postgresql.org/28/2575/ to address $subject. To get an idea for what
eliminating these overheads looks like, take a look at the following
benchmarking results.
Note 1: I've forced the use of generic plan by setting plan_cache_mode
to 'force_generic_plan'
Note 2: The individual TPS figures as measured are quite noisy, though
I just want to show the rough trend with increasing number of
partitions.
pgbench -i -s 10 --partitions={0, 10, 100, 1000}
pgbench -T120 -f test.sql -M prepared
test.sql:
\set aid random(1, 1000000)
update pgbench_accounts set abalance = abalance + 1 where aid = :aid;
Without any of the patches:
0 tps = 13045.485121 (excluding connections establishing)
10 tps = 9358.157433 (excluding connections establishing)
100 tps = 1878.274500 (excluding connections establishing)
1000 tps = 84.684695 (excluding connections establishing)
The slowdown as the partition count increases can be explained by the
fact that UPDATE and DELETE can't currently use runtime partition
pruning. So, even if any given transaction is only updating a single
tuple in a single partition, the plans for *all* partitions are being
initialized and also the ResultRelInfos. That is, a lot of useless
work being done in InitPlan() and ExecInitModifyTable().
With the patches at [1]https://commitfest.postgresql.org/28/2575/ (latest 0001+0002 posted there), whereby the
generic plan for UPDATE can now perform runtime pruning, numbers can
be seen to improve, slightly:
0 tps = 12743.487196 (excluding connections establishing)
10 tps = 12644.240748 (excluding connections establishing)
100 tps = 4158.123345 (excluding connections establishing)
1000 tps = 391.248067 (excluding connections establishing)
So even though runtime pruning enabled by those patches ensures that
the useless plans are left untouched by the executor, the
ResultRelInfos are still being made assuming *all* result relations
will be processed. With the attached patches (0001+0002+0003) that I
want to discuss here in this thread, numbers are further improved:
0 tps = 13419.283168 (excluding connections establishing)
10 tps = 12588.016095 (excluding connections establishing)
100 tps = 8560.824225 (excluding connections establishing)
1000 tps = 1926.553901 (excluding connections establishing)
0001 and 0002 are preparatory patches. 0003 teaches nodeModifyTable.c
to make the ResultRelInfo for a given result relation lazily, that is,
when the plan producing tuples to be updated/deleted actually produces
one that belongs to that relation. So, if a transaction only updates
one tuple, then only one ResultRelInfo would be made. For larger
partition counts, that saves significant amount of work.
However, there's one new loop in ExecInitModifyTable() added by the
patches at [1]https://commitfest.postgresql.org/28/2575/ that loops over all partitions, which I haven't been
able to eliminate so far and I'm seeing it cause significant
bottleneck at higher partition counts. The loop is meant to create a
hash table that maps result relation OIDs to their offsets in the
PlannedStmt.resultRelations list. We need this mapping, because the
ResultRelInfos are accessed from the query-global array using that
offset. One approach that was mentioned by David Rowley at [1]https://commitfest.postgresql.org/28/2575/ to not
have do this mapping is to make the result relation's scan node's
targetlist emit the relation's RT index or ordinal position to begin
with, instead of the table OID, but I haven't figured out a way to do
that.
Having taken care of the ModifyTable overheads (except the one
mentioned in the last paragraph), a few more bottlenecks are seen to
pop up at higher partition counts. Basically, they result from doing
some pre-execution actions on relations contained in the plan by
traversing the flat range table in whole.
1. AcquireExecutorLocks(): locks *all* partitions before executing the
plan tree but runtime pruning allows to skip scanning all but one
2. ExecCheckRTPerms(): checks permissions of *all* partitions before
executing the plan tree, but maybe it's okay to check only the ones
that will be accessed
Problem 1 has been discussed before and David Rowley even developed a
patch that was discussed at [2]/messages/by-id/CAKJS1f_kfRQ3ZpjQyHC7=PK9vrhxiHBQFZ+hc0JCwwnRKkF3hg@mail.gmail.com. The approach taken in the patch was
to delay locking of the partitions contained in a generic plan that
are potentially runtime pruneable, although as also described in the
linked thread, that approach has a race condition whereby a concurrent
session may invalidate the generic plan by altering a partition in the
window between when AcquireExecutorLocks() runs on the plan and the
plan is executed.
Another solution suggested to me by Robert Haas in an off-list
discussion is to teach AcquireExecutorLocks() or the nearby code to
perform EXTERN parameter based pruning before passing the plan tree to
the executor and lock partitions that survive that pruning. It's
perhaps doable if we refactor the ExecFindInitialMatchingSubPlans() to
not require a full-blown execution context. Or maybe we could do
something more invasive by rewriting AcquireExecutorLocks() to walk
the plan tree instead of the flat range table, looking for scan nodes
and nodes that support runtime pruning to lock the appropriate
relations.
Regarding problem 2, I wonder if we shouldn't simply move the
permission check to ExecGetRangeTableRelation(), which will be
performed the first time a given relation is accessed during
execution.
Anyway, applying David's aforementioned patch gives the following numbers:
0 tps = 12325.890487 (excluding connections establishing)
10 tps = 12146.420443 (excluding connections establishing)
100 tps = 12807.850709 (excluding connections establishing)
1000 tps = 7578.652893 (excluding connections establishing)
which suggests that it might be worthwhile try to find a solution for this.
Finally, there's one more place that shows up in perf profiles at
higher partition counts and that is LockReleaseAll(). David,
Tsunakawa-san had worked on a patch [3]/messages/by-id/CAKJS1f-7T9F1xLw5PqgOApcV6YX3WYC4XJHHCpxh8hzcZsA-xA@mail.gmail.com, which still applies and can
be shown to be quite beneficial when generic plans are involved. I
couldn't get it to show major improvement over the above numbers in
this case (for UPDATE that is), but maybe that's because the loop in
ExecInitModifyTable() mentioned above is still in the way.
--
Amit Langote
EnterpriseDB: http://www.enterprisedb.com
[1]: https://commitfest.postgresql.org/28/2575/
[2]: /messages/by-id/CAKJS1f_kfRQ3ZpjQyHC7=PK9vrhxiHBQFZ+hc0JCwwnRKkF3hg@mail.gmail.com
[3]: /messages/by-id/CAKJS1f-7T9F1xLw5PqgOApcV6YX3WYC4XJHHCpxh8hzcZsA-xA@mail.gmail.com
Attachments:
v1-0002-Do-not-set-rootResultRelIndex-unnecessarily.patchapplication/octet-stream; name=v1-0002-Do-not-set-rootResultRelIndex-unnecessarily.patchDownload
From 4a7d35d483794bad2b65ffa894e5ae30ad8cfe87 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 13:12:21 +0900
Subject: [PATCH v1 2/3] Do not set rootResultRelIndex unnecessarily
It's set in ModifyTable for all INSERT, UPDATE, and DELETE, however
only needed for the latter two operations.
---
src/backend/optimizer/plan/setrefs.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 3eb0fc1..5001966 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -935,8 +935,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
* If the main target relation is a partitioned table, also
* add the partition root's RT index to rootResultRelations,
* and remember its index in that list in rootResultRelIndex.
+ * We do this only for UPDATE/DELETE though, because in only
+ * in their case do we need to process the root relation
+ * separately from other result relations.
*/
- if (splan->rootRelation)
+ if (splan->rootRelation && splan->operation != CMD_INSERT)
{
splan->rootResultRelIndex =
list_length(root->glob->rootResultRelations);
--
1.8.3.1
v1-0001-Revise-how-some-FDW-executor-APIs-obtain-ResultRe.patchapplication/octet-stream; name=v1-0001-Revise-how-some-FDW-executor-APIs-obtain-ResultRe.patchDownload
From 9eb3162b84bb0b12d5b9ce364d67602c41d87dc4 Mon Sep 17 00:00:00 2001
From: Etsuro Fujita <efujita@postgresql.org>
Date: Thu, 8 Aug 2019 21:41:12 +0900
Subject: [PATCH v1 1/3] Revise how some FDW executor APIs obtain ResultRelInfo
This adds a field to ForeignScan node to get the index of the target
foreign table in the global list of query result relations (the
es_result_relations array).
So, BeginDirectModify() uses that index to get the RT index of the
target relation from the global list of target relation RT indexes.
Also, IterateDirectModify() uses that index to get the ResultRelInfo
from es_result_relations.
Amit Langote, Etsuro Fujita
Discussion: https://postgr.es/m/20190718010911.l6xcdv6birtxiei4@alap3.anarazel.de
---
contrib/postgres_fdw/postgres_fdw.c | 34 +++++++++++++++++++++++++++------
doc/src/sgml/fdwhandler.sgml | 5 +++--
src/backend/executor/nodeForeignscan.c | 11 +++++++----
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 2 ++
src/backend/optimizer/plan/setrefs.c | 15 +++++++++++++++
src/include/nodes/plannodes.h | 3 +++
9 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 697ec68..4a495bf 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -218,6 +218,7 @@ typedef struct PgFdwDirectModifyState
int num_tuples; /* # of result tuples */
int next_tuple; /* index of next one to return */
Relation resultRel; /* relcache entry for the target relation */
+ ResultRelInfo *resultRelInfo; /* ResultRelInfo for the target relation */
AttrNumber *attnoMap; /* array of attnums of input user columns */
AttrNumber ctidAttno; /* attnum of input ctid column */
AttrNumber oidAttno; /* attnum of input oid column */
@@ -361,7 +362,8 @@ static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
-static void postgresBeginDirectModify(ForeignScanState *node, int eflags);
+static void postgresBeginDirectModify(ForeignScanState *node,
+ int eflags);
static TupleTableSlot *postgresIterateDirectModify(ForeignScanState *node);
static void postgresEndDirectModify(ForeignScanState *node);
static void postgresExplainForeignScan(ForeignScanState *node,
@@ -2321,6 +2323,11 @@ postgresPlanDirectModify(PlannerInfo *root,
rebuild_fdw_scan_tlist(fscan, returningList);
}
+ /*
+ * Set the index of the subplan result rel.
+ */
+ fscan->resultRelIndex = subplan_index;
+
table_close(rel, NoLock);
return true;
}
@@ -2330,10 +2337,12 @@ postgresPlanDirectModify(PlannerInfo *root,
* Prepare a direct foreign table modification
*/
static void
-postgresBeginDirectModify(ForeignScanState *node, int eflags)
+postgresBeginDirectModify(ForeignScanState *node,
+ int eflags)
{
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
PgFdwDirectModifyState *dmstate;
Index rtindex;
RangeTblEntry *rte;
@@ -2358,7 +2367,8 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does.
*/
- rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
+ Assert(fsplan->resultRelIndex >= 0);
+ rtindex = list_nth_int(resultRelations, fsplan->resultRelIndex);
rte = exec_rt_fetch(rtindex, estate);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
@@ -2391,6 +2401,9 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
dmstate->rel = NULL;
}
+ /* ResultRelInfo is set when first needed by IterateDirectModify(). */
+ dmstate->resultRelInfo = NULL;
+
/* Initialize state variable */
dmstate->num_tuples = -1; /* -1 means not set yet */
@@ -2453,7 +2466,16 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = dmstate->resultRelInfo;
+
+ if (resultRelInfo == NULL)
+ {
+ ForeignScan *fscan = (ForeignScan *) node->ss.ps.plan;
+
+ resultRelInfo = &estate->es_result_relations[fscan->resultRelIndex];
+ Assert(resultRelInfo != NULL);
+ dmstate->resultRelInfo = resultRelInfo;
+ }
/*
* If this is the first call after Begin, execute the statement.
@@ -4089,7 +4111,7 @@ get_returning_data(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = dmstate->resultRelInfo;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
TupleTableSlot *resultSlot;
@@ -4236,7 +4258,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
TupleTableSlot *slot,
EState *estate)
{
- ResultRelInfo *relInfo = estate->es_result_relation_info;
+ ResultRelInfo *relInfo = dmstate->resultRelInfo;
TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
TupleTableSlot *resultSlot;
Datum *values;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 6587678..7439cd1 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -917,8 +917,9 @@ IterateDirectModify(ForeignScanState *node);
tuple table slot (the node's <structfield>ScanTupleSlot</structfield> should be
used for this purpose). The data that was actually inserted, updated
or deleted must be stored in the
- <literal>es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
- of the node's <structname>EState</structname>.
+ <literal>ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
+ of the target foreign table's <structname>ResultRelInfo</structname>
+ passed to <function>BeginDirectModify</function>.
Return NULL if no more rows are available.
Note that this is called in a short-lived memory context that will be
reset between invocations. Create a memory context in
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 513471a..ec5f166 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -221,12 +221,15 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan or the direct modification.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
+ else
+ {
+ Assert(node->resultRelIndex >= 0);
+ fdwroutine->BeginDirectModify(scanstate, eflags);
+ }
return scanstate;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c988c96..4d4b434 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -762,6 +762,7 @@ _copyForeignScan(const ForeignScan *from)
COPY_NODE_FIELD(fdw_recheck_quals);
COPY_BITMAPSET_FIELD(fs_relids);
COPY_SCALAR_FIELD(fsSystemCol);
+ COPY_SCALAR_FIELD(resultRelIndex);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d168ecc..0997e74 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -699,6 +699,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
WRITE_NODE_FIELD(fdw_recheck_quals);
WRITE_BITMAPSET_FIELD(fs_relids);
WRITE_BOOL_FIELD(fsSystemCol);
+ WRITE_INT_FIELD(resultRelIndex);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 92bb7ad..872cf10 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2018,6 +2018,7 @@ _readForeignScan(void)
READ_NODE_FIELD(fdw_recheck_quals);
READ_BITMAPSET_FIELD(fs_relids);
READ_BOOL_FIELD(fsSystemCol);
+ READ_INT_FIELD(resultRelIndex);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index da4a804..1078704 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -5557,6 +5557,8 @@ make_foreignscan(List *qptlist,
node->fs_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;
+ /* resultRelIndex will be set by PlanDirectModify/setrefs.c, if needed */
+ node->resultRelIndex = -1;
return node;
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6a52c6c..3eb0fc1 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -906,6 +906,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
rc->rti += rtoffset;
rc->prti += rtoffset;
}
+ /*
+ * Caution: Do not change the relative ordering of this loop
+ * and the statement below that adds the result relations to
+ * root->glob->resultRelations, because we need to use the
+ * current value of list_length(root->glob->resultRelations)
+ * in some plans.
+ */
foreach(l, splan->plans)
{
lfirst(l) = set_plan_refs(root,
@@ -1245,6 +1252,14 @@ set_foreignscan_references(PlannerInfo *root,
}
fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
+
+ /*
+ * Adjust resultRelIndex if it's valid (note that we are called before
+ * adding the RT indexes of ModifyTable result relations to the global
+ * list)
+ */
+ if (fscan->resultRelIndex >= 0)
+ fscan->resultRelIndex += list_length(root->glob->resultRelations);
}
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 909820b..ffe1830 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -621,6 +621,9 @@ typedef struct ForeignScan
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
Bitmapset *fs_relids; /* RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
+ int resultRelIndex; /* index of foreign table in the list of query
+ * result relations for INSERT/UPDATE/DELETE;
+ * -1 for SELECT */
} ForeignScan;
/* ----------------
--
1.8.3.1
v1-0003-Delay-initializing-UPDATE-DELETE-ResultRelInfos.patchapplication/octet-stream; name=v1-0003-Delay-initializing-UPDATE-DELETE-ResultRelInfos.patchDownload
From d2684f544d80f456a8a6cf001af7c12815eb2fbb Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 19:41:49 +0900
Subject: [PATCH v1 3/3] Delay initializing UPDATE/DELETE ResultRelInfos
Currently, InitPlan() makes one for each of the result relations in
PlannedStmt.resultRelations which may be a long list if the planner
couldn't perform any pruning, such as when making a generic plan.
Instead, make the ResultRelInfo of a given result relation only when
the plan actually produces a tuple to update/delete that belongs to
that relation.
As part of this, the tuple conversion map to convert from child
format to root format that used to be part of TransitionCaptureState
is now moved into ResultRelInfo so that it can also be initialized
lazily for a given child result relation along with its
ResultRelInfo.
---
contrib/postgres_fdw/postgres_fdw.c | 2 +-
src/backend/commands/copy.c | 37 +-
src/backend/commands/explain.c | 41 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/commands/trigger.c | 9 +-
src/backend/executor/execMain.c | 125 ++--
src/backend/executor/execPartition.c | 26 +-
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 936 +++++++++++++-------------
src/backend/replication/logical/worker.c | 2 +-
src/include/commands/trigger.h | 12 -
src/include/executor/executor.h | 4 +
src/include/executor/nodeModifyTable.h | 1 -
src/include/nodes/execnodes.h | 15 +-
src/test/regress/expected/partition_prune.out | 47 ++
src/test/regress/sql/partition_prune.sql | 12 +
16 files changed, 658 insertions(+), 617 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 4a495bf..47109f2 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -2472,7 +2472,7 @@ postgresIterateDirectModify(ForeignScanState *node)
{
ForeignScan *fscan = (ForeignScan *) node->ss.ps.plan;
- resultRelInfo = &estate->es_result_relations[fscan->resultRelIndex];
+ resultRelInfo = estate->es_result_relations[fscan->resultRelIndex];
Assert(resultRelInfo != NULL);
dmstate->resultRelInfo = resultRelInfo;
}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6b1fd6d..749905e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2788,7 +2788,7 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
@@ -2802,7 +2802,7 @@ CopyFrom(CopyState cstate)
mtstate->ps.plan = NULL;
mtstate->ps.state = estate;
mtstate->operation = CMD_INSERT;
- mtstate->resultRelInfo = estate->es_result_relations;
+ mtstate->resultRelInfo = resultRelInfo;
if (resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
@@ -3067,32 +3067,15 @@ CopyFrom(CopyState cstate)
estate->es_result_relation_info = resultRelInfo;
/*
- * If we're capturing transition tuples, we might need to convert
- * from the partition rowtype to root rowtype.
+ * If we're capturing transition tuples and there are no BR
+ * triggers to modify the row, we can simply put the original
+ * tuple into the transition tuplestore.
*/
- if (cstate->transition_capture != NULL)
- {
- if (has_before_insert_row_trig)
- {
- /*
- * If there are any BEFORE triggers on the partition,
- * we'll have to be ready to convert their result back to
- * tuplestore format.
- */
- cstate->transition_capture->tcs_original_insert_tuple = NULL;
- cstate->transition_capture->tcs_map =
- resultRelInfo->ri_PartitionInfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted
- * tuple, to avoid a needless round trip conversion.
- */
- cstate->transition_capture->tcs_original_insert_tuple = myslot;
- cstate->transition_capture->tcs_map = NULL;
- }
- }
+ if (cstate->transition_capture != NULL &&
+ !has_before_insert_row_trig)
+ cstate->transition_capture->tcs_original_insert_tuple = myslot;
+ else if (cstate->transition_capture != NULL)
+ cstate->transition_capture->tcs_original_insert_tuple = NULL;
/*
* We might need to convert from the root rowtype to the partition
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 2a087bf..3b407e0 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -795,13 +796,21 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
show_relname = (numrels > 1 || numrootrels > 0 ||
routerels != NIL || targrels != NIL);
- rInfo = queryDesc->estate->es_result_relations;
- for (nr = 0; nr < numrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ for (nr = 0; nr < numrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_result_relations[nr];
- rInfo = queryDesc->estate->es_root_result_relations;
- for (nr = 0; nr < numrootrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
+
+ for (nr = 0; nr < numrootrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_root_result_relations[nr];
+
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
foreach(l, routerels)
{
@@ -3676,15 +3685,29 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nrels > 1 ||
(mtstate->mt_nrels == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ linitial_int(node->resultRelations) != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nrels; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
- FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
+ /*
+ * Get the ResultRelInfo of to show the information of this result
+ * relation. When the ModifyTable is actually performed (ANALYZE is
+ * on), we pass false for create_it, so as to show only those that
+ * were actually initialized during the execution due to some tuple
+ * in them getting modified.
+ */
+ ResultRelInfo *resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex + j,
+ !es->analyze);
+ FdwRoutine *fdwroutine;
+
+ if (resultRelInfo == NULL)
+ continue;
+
+ fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f..0342429 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1789,7 +1789,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
0);
resultRelInfo++;
}
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations = &resultRelInfos;
estate->es_num_result_relations = list_length(rels);
/*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 58a5111..98c83a2 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -34,6 +34,7 @@
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
+#include "executor/execPartition.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
@@ -4295,10 +4296,6 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
* If there are no triggers in 'trigdesc' that request relevant transition
* tables, then return NULL.
*
- * The resulting object can be passed to the ExecAR* functions. The caller
- * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing
- * with child tables.
- *
* Note that we copy the flags from a parent table into this struct (rather
* than subsequently using the relation's TriggerDesc directly) so that we can
* use it to control collection of transition tuples from child tables.
@@ -5392,7 +5389,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = transition_capture->tcs_map;
+ PartitionRoutingInfo *pinfo = relinfo->ri_PartitionInfo;
+ TupleConversionMap *map = pinfo ? pinfo->pi_PartitionToRootMap :
+ relinfo->ri_childToRootMap;
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 166486d..5f1b560 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,35 +828,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_plannedstmt = plannedstmt;
/*
- * Initialize ResultRelInfo data structures, and open the result rels.
+ * Allocate space for ResultRelInfo pointers that will be filled later.
+ * See ExecGetResultRelInfo() and ExecGetRootResultRelInfo().
*/
if (plannedstmt->resultRelations)
{
List *resultRelations = plannedstmt->resultRelations;
int numResultRelations = list_length(resultRelations);
- ResultRelInfo *resultRelInfos;
- ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, resultRelations)
- {
- Index resultRelationIndex = lfirst_int(l);
- Relation resultRelation;
-
- resultRelation = ExecGetRangeTableRelation(estate,
- resultRelationIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelation,
- resultRelationIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
estate->es_num_result_relations = numResultRelations;
-
/* es_result_relation_info is NULL except when within ModifyTable */
estate->es_result_relation_info = NULL;
@@ -869,25 +851,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
{
int num_roots = list_length(plannedstmt->rootResultRelations);
- resultRelInfos = (ResultRelInfo *)
- palloc(num_roots * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, plannedstmt->rootResultRelations)
- {
- Index resultRelIndex = lfirst_int(l);
- Relation resultRelDesc;
-
- resultRelDesc = ExecGetRangeTableRelation(estate,
- resultRelIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelDesc,
- resultRelIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
-
- estate->es_root_result_relations = resultRelInfos;
+ estate->es_root_result_relations =
+ palloc0(num_roots * sizeof(ResultRelInfo *));
estate->es_num_root_result_relations = num_roots;
}
else
@@ -1376,24 +1341,18 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
MemoryContext oldcontext;
/* First, search through the query result relations */
- rInfo = estate->es_result_relations;
- nr = estate->es_num_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/* Second, search through the root result relations, if any */
- rInfo = estate->es_root_result_relations;
- nr = estate->es_num_root_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_root_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_root_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/*
@@ -1559,14 +1518,21 @@ ExecEndPlan(PlanState *planstate, EState *estate)
ExecResetTupleTable(estate->es_tupleTable, false);
/*
- * close indexes of result relation(s) if any. (Rels themselves get
- * closed next.)
+ * Close indexes of result relation(s) if any. (Rels themselves get
+ * closed next.) Also, allow the result relation's FDWs to shut down.
*/
- resultRelInfo = estate->es_result_relations;
- for (i = estate->es_num_result_relations; i > 0; i--)
+ for (i = 0; i < estate->es_num_result_relations; i++)
{
- ExecCloseIndices(resultRelInfo);
- resultRelInfo++;
+ resultRelInfo = estate->es_result_relations[i];
+ if (resultRelInfo)
+ {
+ ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
+ }
}
/*
@@ -2795,23 +2761,42 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree)
{
int numResultRelations = parentestate->es_num_result_relations;
int numRootResultRels = parentestate->es_num_root_result_relations;
- ResultRelInfo *resultRelInfos;
+ int i;
+ ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_result_relations,
- numResultRelations * sizeof(ResultRelInfo));
- rcestate->es_result_relations = resultRelInfos;
+ rcestate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
+ for (i = 0; i < numResultRelations; i++)
+ {
+ if (parentestate->es_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_result_relations = numResultRelations;
/* Also transfer partitioned root result relations. */
if (numRootResultRels > 0)
{
- resultRelInfos = (ResultRelInfo *)
- palloc(numRootResultRels * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_root_result_relations,
- numRootResultRels * sizeof(ResultRelInfo));
- rcestate->es_root_result_relations = resultRelInfos;
+ rcestate->es_root_result_relations =
+ palloc0(numRootResultRels * sizeof(ResultRelInfo *));
+ for (i = 0; i < numRootResultRels; i++)
+ {
+ if (parentestate->es_root_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_root_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_root_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_root_result_relations = numRootResultRels;
}
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 24afcb2..b425859 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -447,7 +447,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Index firstVarno = node ? linitial_int(node->resultRelations) : 0;
+ Relation firstResultRel = firstVarno > 0 ?
+ ExecGetRangeTableRelation(estate, firstVarno) : NULL;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -495,7 +497,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -559,7 +560,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -618,7 +618,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -864,9 +863,22 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
if (mtstate &&
(mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
{
- partrouteinfo->pi_PartitionToRootMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
- RelationGetDescr(partRelInfo->ri_PartitionRoot));
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+
+ /*
+ * If the partition appears to be an UPDATE result relation, the map
+ * would already have been initialized by ExecBuildResultRelInfo(); use
+ * that one instead of building one from scratch. To distinguish
+ * UPDATE result relations from tuple-routing result relations, we rely
+ * on the fact that each of the former has a distinct RT index.
+ */
+ if (node && node->rootRelation != partRelInfo->ri_RangeTableIndex)
+ partrouteinfo->pi_PartitionToRootMap =
+ partRelInfo->ri_childToRootMap;
+ else
+ partrouteinfo->pi_PartitionToRootMap =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
+ RelationGetDescr(partRelInfo->ri_PartitionRoot));
}
else
partrouteinfo->pi_PartitionToRootMap = NULL;
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d0e65b8..a67c023 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -711,13 +711,13 @@ ExecCreateScanSlotFromOuterPlan(EState *estate,
bool
ExecRelationIsTargetRelation(EState *estate, Index scanrelid)
{
- ResultRelInfo *resultRelInfos;
+ ResultRelInfo **resultRelInfos;
int i;
resultRelInfos = estate->es_result_relations;
for (i = 0; i < estate->es_num_result_relations; i++)
{
- if (resultRelInfos[i].ri_RangeTableIndex == scanrelid)
+ if (resultRelInfos[i]->ri_RangeTableIndex == scanrelid)
return true;
}
return false;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 74c12be..3f611a7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,11 +72,10 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
-static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
-static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
- int whichplan);
static TupleTableSlot *ExecGetNewInsertTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot);
+static ResultRelInfo *ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int resultRelIndex);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1060,6 +1059,9 @@ typedef struct SubplanResultRelHashElem
static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *lc;
HASHCTL ctl;
HTAB *htab;
int i;
@@ -1075,18 +1077,19 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate)
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
mtstate->mt_subplan_resultrel_hash = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nrels; i++)
+ /* Map result relation OIDs to their index in es_result_relations. */
+ i = 0;
+ foreach(lc, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(lc);
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
+ Oid partoid = exec_rt_fetch(rti, estate)->relid;
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->whichrel = i;
+ elem->whichrel = i++;
}
}
@@ -1105,6 +1108,7 @@ ResultRelInfo *
ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
Oid reloid, int *whichrel)
{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
SubplanResultRelHashElem *elem;
Assert(mtstate->mt_subplan_resultrel_hash != NULL);
@@ -1115,13 +1119,416 @@ ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
if (elem)
{
*whichrel = elem->whichrel;
- return mtstate->resultRelInfo + elem->whichrel;
+ return ExecGetResultRelInfo(mtstate,
+ node->resultRelIndex + elem->whichrel,
+ true);
}
return NULL;
}
/*
+ * ExecGetResultRelInfo
+ * Returns the result relation at a given offset within the global array
+ * of ResultRelInfos
+ *
+ * If not present and 'create_it' is true, it is created and put at the given
+ * offset for subsequent calls to find.
+ *
+ * This, in conjunction with ExecLookupResultRelByOid, allows lazy
+ * initialization of ResultRelInfos. That can be helpful in the case where
+ * there are multiple result relations due to inheritance but only one or
+ * few actually end up actually having any tuples to update.
+ *
+ * Note: only call from the executor proper or anything that possesses a valid
+ * execution context, that is an EState with a PlannedStmt, because this
+ * depends on finding a valid PlannedStmt to get result relation RT indexes
+ * from.
+ */
+ResultRelInfo *
+ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[resultRelIndex];
+
+ if (resultRelInfo == NULL && create_it)
+ {
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
+ Index rti = list_nth_int(resultRelations, resultRelIndex);
+
+ resultRelInfo = ExecBuildResultRelInfo(mtstate, rti, resultRelIndex);
+ estate->es_result_relations[resultRelIndex] = resultRelInfo;
+ }
+
+ return resultRelInfo;
+}
+
+/*
+ * ExecGetRootResultRelInfo
+ * Like ExecGetResultRelInfo, but for "root" result relations
+ * corresponding to partitioned tables, which are managed separately from
+ * leaf result relations
+ *
+ * Root ResultRelInfos are never created lazily, although it seems better to
+ * have the same interface to avoid exposing ExecBuildResultRelInfo().
+ */
+ResultRelInfo *
+ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *rootRelInfo = estate->es_root_result_relations[rootRelIndex];
+
+ if (rootRelInfo == NULL)
+ {
+ List *rootRelations = estate->es_plannedstmt->rootResultRelations;
+ Index rti = list_nth_int(rootRelations, rootRelIndex);
+
+ rootRelInfo = ExecBuildResultRelInfo(mtstate, rti, rootRelIndex);
+ estate->es_root_result_relations[rootRelIndex] = rootRelInfo;
+ }
+
+ return rootRelInfo;
+}
+
+/*
+ * ExecBuildResultRelInfo
+ * Builds a ResultRelInfo for a result relation with given RT index
+ */
+static ResultRelInfo *
+ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int resultRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ Plan *subplan = linitial(node->plans);
+ CmdType operation = node->operation;
+ int firstRelIndex = node->resultRelIndex;
+ int thisRelIndex = resultRelIndex - firstRelIndex;
+ Relation relation = ExecGetRangeTableRelation(estate, rti);
+ ResultRelInfo *resultRelInfo;
+ List *resultTargetList = NIL;
+ bool need_projection = false;
+ bool update_tuple_routing_needed = false;
+ ListCell *l;
+ int eflags = estate->es_top_eflags;
+ MemoryContext oldcxt;
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, relation, rti, NULL,
+ estate->es_instrument);
+
+ /*
+ * If this is the root result relation of an UPDATE/DELETE, we only need
+ * a minimally valid result relation.
+ */
+ if (rti == node->rootRelation && operation != CMD_INSERT)
+ {
+ MemoryContextSwitchTo(oldcxt);
+ return resultRelInfo;
+ }
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(thisRelIndex,
+ node->fdwDirectModifyPlans);
+
+ /*
+ * Verify result relation is a valid target for the current operation
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /*
+ * 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);
+
+ /* Result relation specific slot to store the plan's output tuple. */
+ mtstate->mt_scans[thisRelIndex] =
+ ExecInitExtraTupleSlot(mtstate->ps.state, mtstate->mt_plan_tupdesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists,
+ thisRelIndex);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ thisRelIndex,
+ eflags);
+ }
+
+ /*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ if (node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists,
+ thisRelIndex);
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* RETURNING list */
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists,
+ thisRelIndex);
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (node->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (node->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(mtstate->mt_nrels == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a slot
+ * of the table's type here, because the slot will be used to insert
+ * into the table, and for RETURNING processing - which may access
+ * system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(node->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (node->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) node->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Prepare to generate tuples suitable for the target relation.
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ if (operation == CMD_INSERT)
+ {
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (!tle->resjunk)
+ resultTargetList = lappend(resultTargetList, tle);
+ else
+ need_projection = true;
+ }
+ }
+ else
+ {
+ resultTargetList = (List *) list_nth(node->updateTargetLists,
+ thisRelIndex);
+ need_projection = true;
+ }
+
+ /*
+ * The clean list must produce a tuple suitable for the result
+ * relation.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, resultTargetList);
+ }
+
+ if (need_projection)
+ {
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ /*
+ * For UPDATE, we use the old tuple to fill up missing values in
+ * the tuple produced by the plan to get the new tuple.
+ */
+ if (operation == CMD_UPDATE)
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildProjectionInfo(resultTargetList,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps,
+ relDesc);
+ }
+
+ /*
+ * For UPDATE/DELETE, find the appropriate junk attr now.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+ /* HACK: we require it to be present for updates. */
+ if (mtstate->operation == CMD_UPDATE &&
+ !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ else
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ /*
+ * For UPDATE on a partitioned tables, tuple routing might be needed if
+ * if the plan says so or a BEFORE UPDATE trigger is present on the
+ * partition which might modify the partition-key values.
+ */
+ if (mtstate->rootResultRelInfo &&
+ operation == CMD_UPDATE &&
+ (node->partColsUpdated ||
+ (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_update_before_row)))
+ update_tuple_routing_needed = true;
+
+ /* Tuple routing state may already have been initialized. */
+ if (update_tuple_routing_needed &&
+ mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rootRel);
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
+ }
+
+ /*
+ * 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.
+ */
+ if (update_tuple_routing_needed ||
+ (mtstate->mt_transition_capture &&
+ mtstate->operation != CMD_INSERT &&
+ thisRelIndex > 0))
+ {
+ Relation targetRel = getTargetResultRelInfo(mtstate)->ri_RelationDesc;
+
+ resultRelInfo->ri_childToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return resultRelInfo;
+}
+
+/*
* ExecGetInsertNewTuple
* This prepares a "new" tuple ready to be put into a result relation
* by removing any junk columns of the tuple produced by a plan.
@@ -1217,7 +1624,6 @@ ExecUpdate(ModifyTableState *mtstate,
TM_Result result;
TM_FailureData tmfd;
List *recheckIndexes = NIL;
- TupleConversionMap *saved_tcs_map = NULL;
/*
* abort the operation if not running transactions
@@ -1342,7 +1748,6 @@ lreplace:;
TupleTableSlot *ret_slot;
TupleTableSlot *epqslot = NULL;
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
- int map_index;
TupleConversionMap *tupconv_map;
/*
@@ -1413,16 +1818,6 @@ lreplace:;
}
/*
- * Updates set the transition capture map only when a new subplan
- * is chosen. But for inserts, it is set for each row. So after
- * INSERT, we need to revert back to the map created for UPDATE;
- * otherwise the next UPDATE will incorrectly use the one created
- * for INSERT. So first save the one created for UPDATE.
- */
- if (mtstate->mt_transition_capture)
- saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
-
- /*
* resultRelInfo is one of the per-subplan resultRelInfos. So we
* should convert the tuple into root's tuple descriptor, since
* ExecInsert() starts the search from root. The tuple conversion
@@ -1430,9 +1825,7 @@ lreplace:;
* retrieve the one for this resultRel, we need to know the
* position of the resultRel in mtstate->resultRelInfo[].
*/
- map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nrels);
- tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
+ tupconv_map = resultRelInfo->ri_childToRootMap;
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1451,11 +1844,13 @@ lreplace:;
/* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
+
+ /*
+ * Reset the transition state that may possibly have been written
+ * by INSERT.
+ */
if (mtstate->mt_transition_capture)
- {
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map = saved_tcs_map;
- }
return ret_slot;
}
@@ -1921,7 +2316,7 @@ static ResultRelInfo *
getTargetResultRelInfo(ModifyTableState *node)
{
/*
- * Note that if the node modifies a partitioned table, node->resultRelInfo
+ * Note that if the node modifies a partitioned table, plan->resultRelInfo
* points to the first leaf partition, not the root table.
*/
if (node->rootResultRelInfo != NULL)
@@ -1984,28 +2379,6 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
RelationGetRelid(targetRelInfo->ri_RelationDesc),
CMD_UPDATE);
-
- /*
- * If we found that we need to collect transition tuples then we may also
- * need tuple conversion maps for any children that have TupleDescs that
- * aren't compatible with the tuplestores. (We can share these maps
- * between the regular and ON CONFLICT cases.)
- */
- if (mtstate->mt_transition_capture != NULL ||
- mtstate->mt_oc_transition_capture != NULL)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
-
- /*
- * Install the conversion map for the first plan for UPDATE and DELETE
- * operations. It will be advanced each time we switch to the next
- * plan. (INSERT operations set it every time, so we need not update
- * mtstate->mt_oc_transition_capture here.)
- */
- if (mtstate->mt_transition_capture && mtstate->operation != CMD_INSERT)
- mtstate->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(mtstate, 0);
- }
}
/*
@@ -2047,37 +2420,16 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
estate->es_result_relation_info = partrel;
/*
- * If we're capturing transition tuples, we might need to convert from the
- * partition rowtype to root partitioned table's rowtype.
+ * If we're capturing transition tuples and there are no BR triggers to
+ * modify the row, we can simply put the original tuple into the
+ * transition tuplestore.
*/
- if (mtstate->mt_transition_capture != NULL)
- {
- if (partrel->ri_TrigDesc &&
- partrel->ri_TrigDesc->trig_insert_before_row)
- {
- /*
- * If there are any BEFORE triggers on the partition, we'll have
- * to be ready to convert their result back to tuplestore format.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted tuple, to
- * avoid a needless round trip conversion.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
- mtstate->mt_transition_capture->tcs_map = NULL;
- }
- }
- if (mtstate->mt_oc_transition_capture != NULL)
- {
- mtstate->mt_oc_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
+ if (mtstate->mt_transition_capture != NULL &&
+ !(partrel->ri_TrigDesc &&
+ partrel->ri_TrigDesc->trig_insert_before_row))
+ mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
+ else if (mtstate->mt_transition_capture != NULL)
+ mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
/*
* Convert the tuple, if necessary.
@@ -2093,58 +2445,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
return slot;
}
-/*
- * Initialize the child-to-root tuple conversion map array for UPDATE subplans.
- *
- * This map array is required to convert the tuple from the subplan result rel
- * to the target table descriptor. This requirement arises for two independent
- * scenarios:
- * 1. For update-tuple-routing.
- * 2. For capturing tuples in transition tables.
- */
-static void
-ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
-{
- ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
- ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
- TupleDesc outdesc;
- int numResultRelInfos = mtstate->mt_nrels;
- int i;
-
- /*
- * Build array of conversion maps from each child's TupleDesc to the one
- * used in the target relation. The map pointers may be NULL when no
- * conversion is necessary, which is hopefully a common case.
- */
-
- /* Get tuple descriptor of the target rel. */
- outdesc = RelationGetDescr(targetRelInfo->ri_RelationDesc);
-
- mtstate->mt_per_subplan_tupconv_maps = (TupleConversionMap **)
- palloc(sizeof(TupleConversionMap *) * numResultRelInfos);
-
- for (i = 0; i < numResultRelInfos; ++i)
- {
- mtstate->mt_per_subplan_tupconv_maps[i] =
- convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
- outdesc);
- }
-}
-
-/*
- * For a given subplan index, get the tuple conversion map.
- */
-static TupleConversionMap *
-tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
-{
- /* If nobody else set the per-subplan array of maps, do so ourselves. */
- if (mtstate->mt_per_subplan_tupconv_maps == NULL)
- ExecSetupChildParentMapForSubplan(mtstate);
-
- Assert(whichplan >= 0 && whichplan < mtstate->mt_nrels);
- return mtstate->mt_per_subplan_tupconv_maps[whichplan];
-}
-
/* ----------------------------------------------------------------
* ExecModifyTable
*
@@ -2159,8 +2459,10 @@ ExecModifyTable(PlanState *pstate)
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
EState *estate = node->ps.state;
CmdType operation = node->operation;
+ int firstRelIndex = ((ModifyTable *) node->ps.plan)->resultRelIndex;
ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo;
+ int whichrel;
PlanState *subplanstate;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
@@ -2202,7 +2504,8 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichrel;
+ whichrel = 0; /* default result rel */
+ resultRelInfo = ExecGetResultRelInfo(node, firstRelIndex + whichrel, true);
subplanstate = node->mt_plans[node->mt_whichplan];
/*
@@ -2262,31 +2565,18 @@ ExecModifyTable(PlanState *pstate)
/* Table OID -> ResultRelInfo. */
resultRelInfo = ExecLookupModifyResultRelByOid(node, tableoid,
- &node->mt_whichrel);
- Assert(node->mt_whichrel >= 0 &&
- node->mt_whichrel < node->mt_nrels);
+ &whichrel);
+ Assert(whichrel >= 0 && whichrel < node->mt_nrels);
estate->es_result_relation_info = resultRelInfo;
-
- /* Prepare to convert transition tuples from this child. */
- if (node->mt_transition_capture != NULL)
- {
- node->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichrel);
- }
- if (node->mt_oc_transition_capture != NULL)
- {
- node->mt_oc_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichrel);
- }
}
/*
* Ensure input tuple is the right format for the target relation.
*/
- if (node->mt_scans[node->mt_whichrel]->tts_ops != planSlot->tts_ops)
+ if (node->mt_scans[whichrel]->tts_ops != planSlot->tts_ops)
{
- ExecCopySlot(node->mt_scans[node->mt_whichrel], planSlot);
- planSlot = node->mt_scans[node->mt_whichrel];
+ ExecCopySlot(node->mt_scans[whichrel], planSlot);
+ planSlot = node->mt_scans[whichrel];
}
/*
@@ -2450,14 +2740,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
CmdType operation = node->operation;
int nplans = list_length(node->plans);
int nrels = list_length(node->resultRelations);
- ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- TupleDesc plan_result_type;
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)));
@@ -2475,14 +2760,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_done = false;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
- mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nrels);
- /* If modifying a partitioned table, initialize the root table info */
- if (node->rootResultRelIndex >= 0)
- mtstate->rootResultRelInfo = estate->es_root_result_relations +
- node->rootResultRelIndex;
-
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2491,7 +2770,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
mtstate->mt_nrels = nrels;
- mtstate->mt_whichrel = 0;
/*
* Call ExecInitNode on the only plan to be executed and save the result
@@ -2502,12 +2780,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* (see contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify()
* as one example).
*/
- saved_resultRelInfo = estate->es_result_relation_info;
- estate->es_result_relation_info = mtstate->resultRelInfo;
subplan = linitial(node->plans);
mtstate->mt_plans[0] = ExecInitNode(subplan, estate, eflags);
- estate->es_result_relation_info = saved_resultRelInfo;
- plan_result_type = ExecGetResultType(mtstate->mt_plans[0]);
+ mtstate->mt_plan_tupdesc = ExecGetResultType(mtstate->mt_plans[0]);
if (mtstate->mt_nrels > 1)
{
@@ -2518,6 +2793,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_tableOidAttno =
ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
Assert(AttributeNumberIsValid(mtstate->mt_tableOidAttno));
+
+ /*
+ * Also, initialize a hash table to look up UPDATE/DELETE result
+ * relations.
+ */
+ ExecHashSubPlanResultRelsByOid(mtstate);
}
/* Initialize some global state for RETURNING projections. */
@@ -2549,226 +2830,25 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
/*
- * Per result relation initializations.
- * TODO: do this lazily.
+ * If modifying a partitioned table, initialize the root table info.
*/
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nrels; i++)
- {
- List *resultTargetList = NIL;
- bool need_projection = false;
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, plan_result_type,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Now init the plan for this result rel */
- estate->es_result_relation_info = resultRelInfo;
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- i,
- eflags);
- }
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- if (node->withCheckOptionLists)
- {
- List *wcoList = (List *) list_nth(node->withCheckOptionLists, i);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- }
- if (node->returningLists)
- {
- List *rlist = (List *) list_nth(node->returningLists, i);
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- slot = mtstate->ps.ps_ResultTupleSlot;
- Assert(slot != NULL);
- econtext = mtstate->ps.ps_ExprContext;
- Assert(econtext != NULL);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- }
-
- /*
- * Prepare to generate tuples suitable for the target relation.
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- if (operation == CMD_INSERT)
- {
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (!tle->resjunk)
- resultTargetList = lappend(resultTargetList, tle);
- else
- need_projection = true;
- }
- }
- else
- {
- resultTargetList = (List *) list_nth(node->updateTargetLists,
- i);
- need_projection = true;
- }
-
- /*
- * The clean list must produce a tuple suitable for the result
- * relation.
- */
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- resultTargetList);
- }
-
- if (need_projection)
- {
- TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
-
- /*
- * For UPDATE, we use the old tuple to fill up missing values in
- * the tuple produced by the plan to get the new tuple.
- */
- if (operation == CMD_UPDATE)
- resultRelInfo->ri_oldTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
- resultRelInfo->ri_newTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* need an expression context to do the projection */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- resultRelInfo->ri_projectNew =
- ExecBuildProjectionInfo(resultTargetList,
- mtstate->ps.ps_ExprContext,
- resultRelInfo->ri_newTupleSlot,
- &mtstate->ps,
- relDesc);
- }
-
- /*
- * For UPDATE/DELETE, find the appropriate junk attr now.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
- if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist,
- "wholerow");
- /* HACK: we require it to be present for updates. */
- if (mtstate->operation == CMD_UPDATE &&
- !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk wholerow column");
- }
- else
- {
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
- if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo++;
- }
+ if (node->rootResultRelIndex >= 0)
+ mtstate->rootResultRelInfo =
+ ExecGetRootResultRelInfo(mtstate, node->rootResultRelIndex);
+ if (mtstate->rootResultRelInfo == NULL)
+ mtstate->resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex, true);
/* Get the target relation */
rel = (getTargetResultRelInfo(mtstate))->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 an INSERT. If an UPDATE might
+ * need it, ExecBuildResultRelInfo will build it when initializing
+ * a partition's ResultRelInfo.
*/
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);
@@ -2780,83 +2860,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * Construct mapping from each of the per-subplan partition attnos to the
- * root attno. This is required when during update row movement the tuple
- * descriptor of a source partition does not match the root partitioned
- * table descriptor. In such a case we need to convert tuples to the root
- * tuple descriptor, because the search for destination partition starts
- * from the root. We'll also need a slot to store these converted tuples.
- * We can skip this setup if it's not a partition key update.
- */
- if (update_tuple_routing_needed)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
- mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- 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);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
- /*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
* EvalPlanQual mechanism needs to be told about them. Locate the
@@ -2884,13 +2887,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize a hash table to look up UPDATE/DELETE result relations by
- * OID if there is more than one.
- */
- if (mtstate->operation != CMD_INSERT && mtstate->mt_nrels > 1)
- ExecHashSubPlanResultRelsByOid(mtstate);
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2920,20 +2916,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nrels; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index a752a12..42c4fc3 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -213,7 +213,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
resultRelInfo = makeNode(ResultRelInfo);
InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index a40ddf5..d1f436c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -45,10 +45,6 @@ typedef struct TriggerData
/*
* The state for capturing old and new tuples into transition tables for a
* single ModifyTable node (or other operation source, e.g. copy.c).
- *
- * This is per-caller to avoid conflicts in setting tcs_map or
- * tcs_original_insert_tuple. Note, however, that the pointed-to
- * private data may be shared across multiple callers.
*/
struct AfterTriggersTableData; /* private in trigger.c */
@@ -66,14 +62,6 @@ typedef struct TransitionCaptureState
bool tcs_insert_new_table;
/*
- * For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the
- * new and old tuples from a child table's format to the format of the
- * relation named in a query so that it is compatible with the transition
- * tuplestores. The caller must store the conversion map here if so.
- */
- TupleConversionMap *tcs_map;
-
- /*
* For INSERT and COPY, it would be wasteful to convert tuples from child
* format to parent format after they have already been converted in the
* opposite direction during routing. In that case we bypass conversion
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1f4efd6..317e739 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -606,6 +606,10 @@ extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
/* prototypes from functions in nodeModifyTable.c */
extern ResultRelInfo *ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
Oid reloid, int *whichrel);
+extern ResultRelInfo *ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it);
+extern ResultRelInfo *ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex);
+
/* needed by trigger.c */
extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 4ec4ebd..a30f5cf 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -20,5 +20,4 @@ extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, Cmd
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
-
#endif /* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 88ac0b2..a1d2b2b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -494,6 +494,12 @@ typedef struct ResultRelInfo
TupleTableSlot *ri_oldTupleSlot;
TupleTableSlot *ri_newTupleSlot;
ProjectionInfo *ri_projectNew;
+
+ /*
+ * Map to convert child sublan tuples to root parent format, set iff
+ * either update row movement or transition tuple capture is active.
+ */
+ TupleConversionMap *ri_childToRootMap;
} ResultRelInfo;
/* ----------------
@@ -525,7 +531,7 @@ typedef struct EState
CommandId es_output_cid;
/* Info about target table(s) for insert/update/delete queries: */
- ResultRelInfo *es_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_result_relations; /* array of ResultRelInfo pointers */
int es_num_result_relations; /* length of array */
ResultRelInfo *es_result_relation_info; /* currently active array elt */
@@ -535,7 +541,8 @@ typedef struct EState
* es_result_relations, but we need access to the roots for firing
* triggers and for runtime tuple routing.
*/
- ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_root_result_relations; /* array of ResultRelInfo
+ * pointers */
int es_num_root_result_relations; /* length of the array */
PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */
@@ -1173,7 +1180,7 @@ typedef struct ModifyTableState
int mt_nplans; /* number of plans in mt_plans (only 1!) */
int mt_whichplan; /* which one is being executed (always 0th!) */
int mt_nrels; /* number of result rels in the arrays */
- int mt_whichrel; /* Array index of target rel being targeted */
+ TupleDesc mt_plan_tupdesc; /* TupleDesc of plan's output */
TupleTableSlot **mt_scans; /* input tuple for each result relation */
/*
@@ -1182,7 +1189,7 @@ typedef struct ModifyTableState
*/
int mt_tableOidAttno;
- ResultRelInfo *resultRelInfo; /* Target relations */
+ ResultRelInfo *resultRelInfo; /* Target relation */
HTAB *mt_subplan_resultrel_hash; /* hash table to look up result
* relation by OID. */
ResultRelInfo *rootResultRelInfo; /* root target relation (partitioned
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index a295c08..ea3200a 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -1837,6 +1837,53 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
Filter: ((b >= $1) AND (b <= $2) AND (a < $0))
(10 rows)
+-- Runtime pruning for UPDATE/DELETE (mainly notice result relations listed)
+prepare upd_q (int, int) as
+update ab set a = a where a = $1 and b = $2;
+-- PARAM_EXTERN
+explain (analyze, costs off, summary off, timing off) execute upd_q (1, 1);
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on ab (actual rows=0 loops=1)
+ Update on ab_a1_b1 ab_1
+ -> Append (actual rows=0 loops=1)
+ Subplans Removed: 8
+ -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Filter: ((a = $1) AND (b = $2))
+(6 rows)
+
+-- PARAM_EXEC
+explain (analyze, costs off, summary off, timing off)
+update ab set a = a where a = (select 1) and b = (select 1);
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on ab (actual rows=0 loops=1)
+ Update on ab_a1_b1 ab_1
+ InitPlan 1 (returns $0)
+ -> Result (actual rows=1 loops=1)
+ InitPlan 2 (returns $1)
+ -> Result (actual rows=1 loops=1)
+ -> Append (actual rows=0 loops=1)
+ -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a1_b2 ab_2 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a1_b3 ab_3 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b1 ab_4 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b2 ab_5 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b3 ab_6 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b1 ab_7 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b2 ab_8 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b3 ab_9 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+(25 rows)
+
-- Test a backwards Append scan
create table list_part (a int) partition by list (a);
create table list_part1 partition of list_part for values in (1);
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 6658455..e7b617d 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -389,6 +389,18 @@ select a from ab where b between $1 and $2 and a < (select 3);
explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
+-- Runtime pruning for UPDATE/DELETE (mainly notice result relations listed)
+
+prepare upd_q (int, int) as
+update ab set a = a where a = $1 and b = $2;
+
+-- PARAM_EXTERN
+explain (analyze, costs off, summary off, timing off) execute upd_q (1, 1);
+
+-- PARAM_EXEC
+explain (analyze, costs off, summary off, timing off)
+update ab set a = a where a = (select 1) and b = (select 1);
+
-- Test a backwards Append scan
create table list_part (a int) partition by list (a);
create table list_part1 partition of list_part for values in (1);
--
1.8.3.1
On Fri, Jun 26, 2020 at 9:36 PM Amit Langote <amitlangote09@gmail.com> wrote:
I would like to discuss a refactoring patch that builds on top of the
patches at [1] to address $subject.
I've added this to the next CF: https://commitfest.postgresql.org/28/2621/
--
Amit Langote
EnterpriseDB: http://www.enterprisedb.com
On Sat, 27 Jun 2020 at 00:36, Amit Langote <amitlangote09@gmail.com> wrote:
2. ExecCheckRTPerms(): checks permissions of *all* partitions before
executing the plan tree, but maybe it's okay to check only the ones
that will be accessed
I don't think it needs to be quite as complex as that.
expand_single_inheritance_child will set the
RangeTblEntry.requiredPerms to 0, so we never need to check
permissions on a partition. The overhead of permission checking when
there are many partitions is just down to the fact that
ExecCheckRTPerms() loops over the entire rangetable and calls
ExecCheckRTEPerms for each one. ExecCheckRTEPerms() does have very
little work to do when requiredPerms is 0, but the loop itself and the
function call overhead show up when you remove the other bottlenecks.
I have a patch somewhere that just had the planner add the RTindexes
with a non-zero requiredPerms and set that in the plan so that
ExecCheckRTPerms could just look at the ones that actually needed
something checked. There's a slight disadvantage there that for
queries to non-partitioned tables that we need to build a Bitmapset
that has all items from the rangetable. That's likely a small
overhead, but not free, so perhaps there is a better way.
David
On Mon, Jun 29, 2020 at 10:39 AM David Rowley <dgrowleyml@gmail.com> wrote:
On Sat, 27 Jun 2020 at 00:36, Amit Langote <amitlangote09@gmail.com> wrote:
2. ExecCheckRTPerms(): checks permissions of *all* partitions before
executing the plan tree, but maybe it's okay to check only the ones
that will be accessedI don't think it needs to be quite as complex as that.
expand_single_inheritance_child will set the
RangeTblEntry.requiredPerms to 0, so we never need to check
permissions on a partition. The overhead of permission checking when
there are many partitions is just down to the fact that
ExecCheckRTPerms() loops over the entire rangetable and calls
ExecCheckRTEPerms for each one. ExecCheckRTEPerms() does have very
little work to do when requiredPerms is 0, but the loop itself and the
function call overhead show up when you remove the other bottlenecks.
I had forgotten that we set requiredPerms to 0 for the inheritance child tables.
I have a patch somewhere that just had the planner add the RTindexes
with a non-zero requiredPerms and set that in the plan so that
ExecCheckRTPerms could just look at the ones that actually needed
something checked. There's a slight disadvantage there that for
queries to non-partitioned tables that we need to build a Bitmapset
that has all items from the rangetable. That's likely a small
overhead, but not free, so perhaps there is a better way.
I can't think of anything for this that doesn't involve having one
more list of RTEs or bitmapset of RT indexes in PlannedStmt.
--
Amit Langote
EnterpriseDB: http://www.enterprisedb.com
On Fri, Jun 26, 2020 at 9:36 PM Amit Langote <amitlangote09@gmail.com> wrote:
I would like to discuss a refactoring patch that builds on top of the
patches at [1] to address $subject.
I forgot to update a place in postgres_fdw causing one of its tests to crash.
Fixed in the attached updated version.
--
Amit Langote
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v2-0003-Delay-initializing-UPDATE-DELETE-ResultRelInfos.patchapplication/octet-stream; name=v2-0003-Delay-initializing-UPDATE-DELETE-ResultRelInfos.patchDownload
From a6986597728c42c1fc6d717ac9cfcce772b3510f Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 19:41:49 +0900
Subject: [PATCH v2 3/3] Delay initializing UPDATE/DELETE ResultRelInfos
Currently, InitPlan() makes one for each of the result relations in
PlannedStmt.resultRelations which may be a long list if the planner
couldn't perform any pruning, such as when making a generic plan.
Instead, make the ResultRelInfo of a given result relation only when
the plan actually produces a tuple to update/delete that belongs to
that relation.
As part of this, the tuple conversion map to convert from child
format to root format that used to be part of TransitionCaptureState
is now moved into ResultRelInfo so that it can also be initialized
lazily for a given child result relation along with its
ResultRelInfo.
---
contrib/postgres_fdw/postgres_fdw.c | 4 +-
src/backend/commands/copy.c | 37 +-
src/backend/commands/explain.c | 41 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/commands/trigger.c | 9 +-
src/backend/executor/execMain.c | 125 ++--
src/backend/executor/execPartition.c | 26 +-
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 936 +++++++++++++-------------
src/backend/replication/logical/worker.c | 2 +-
src/include/commands/trigger.h | 12 -
src/include/executor/executor.h | 4 +
src/include/executor/nodeModifyTable.h | 1 -
src/include/nodes/execnodes.h | 15 +-
src/test/regress/expected/partition_prune.out | 47 ++
src/test/regress/sql/partition_prune.sql | 12 +
16 files changed, 659 insertions(+), 618 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 4a495bf..f606dee 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1994,7 +1994,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
*/
if (plan && plan->operation == CMD_UPDATE &&
resultRelation == plan->rootRelation)
- resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+ resultRelation = linitial_int(plan->resultRelations);
}
/* Construct the SQL command string. */
@@ -2472,7 +2472,7 @@ postgresIterateDirectModify(ForeignScanState *node)
{
ForeignScan *fscan = (ForeignScan *) node->ss.ps.plan;
- resultRelInfo = &estate->es_result_relations[fscan->resultRelIndex];
+ resultRelInfo = estate->es_result_relations[fscan->resultRelIndex];
Assert(resultRelInfo != NULL);
dmstate->resultRelInfo = resultRelInfo;
}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3e199bd..581d28f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2784,7 +2784,7 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
@@ -2798,7 +2798,7 @@ CopyFrom(CopyState cstate)
mtstate->ps.plan = NULL;
mtstate->ps.state = estate;
mtstate->operation = CMD_INSERT;
- mtstate->resultRelInfo = estate->es_result_relations;
+ mtstate->resultRelInfo = resultRelInfo;
if (resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
@@ -3063,32 +3063,15 @@ CopyFrom(CopyState cstate)
estate->es_result_relation_info = resultRelInfo;
/*
- * If we're capturing transition tuples, we might need to convert
- * from the partition rowtype to root rowtype.
+ * If we're capturing transition tuples and there are no BR
+ * triggers to modify the row, we can simply put the original
+ * tuple into the transition tuplestore.
*/
- if (cstate->transition_capture != NULL)
- {
- if (has_before_insert_row_trig)
- {
- /*
- * If there are any BEFORE triggers on the partition,
- * we'll have to be ready to convert their result back to
- * tuplestore format.
- */
- cstate->transition_capture->tcs_original_insert_tuple = NULL;
- cstate->transition_capture->tcs_map =
- resultRelInfo->ri_PartitionInfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted
- * tuple, to avoid a needless round trip conversion.
- */
- cstate->transition_capture->tcs_original_insert_tuple = myslot;
- cstate->transition_capture->tcs_map = NULL;
- }
- }
+ if (cstate->transition_capture != NULL &&
+ !has_before_insert_row_trig)
+ cstate->transition_capture->tcs_original_insert_tuple = myslot;
+ else if (cstate->transition_capture != NULL)
+ cstate->transition_capture->tcs_original_insert_tuple = NULL;
/*
* We might need to convert from the root rowtype to the partition
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 37e7ae1..17a4baa 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -795,13 +796,21 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
show_relname = (numrels > 1 || numrootrels > 0 ||
routerels != NIL || targrels != NIL);
- rInfo = queryDesc->estate->es_result_relations;
- for (nr = 0; nr < numrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ for (nr = 0; nr < numrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_result_relations[nr];
- rInfo = queryDesc->estate->es_root_result_relations;
- for (nr = 0; nr < numrootrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
+
+ for (nr = 0; nr < numrootrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_root_result_relations[nr];
+
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
foreach(l, routerels)
{
@@ -3669,15 +3678,29 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nrels > 1 ||
(mtstate->mt_nrels == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ linitial_int(node->resultRelations) != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nrels; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
- FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
+ /*
+ * Get the ResultRelInfo of to show the information of this result
+ * relation. When the ModifyTable is actually performed (ANALYZE is
+ * on), we pass false for create_it, so as to show only those that
+ * were actually initialized during the execution due to some tuple
+ * in them getting modified.
+ */
+ ResultRelInfo *resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex + j,
+ !es->analyze);
+ FdwRoutine *fdwroutine;
+
+ if (resultRelInfo == NULL)
+ continue;
+
+ fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f..0342429 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1789,7 +1789,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
0);
resultRelInfo++;
}
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations = &resultRelInfos;
estate->es_num_result_relations = list_length(rels);
/*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 58a5111..98c83a2 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -34,6 +34,7 @@
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
+#include "executor/execPartition.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
@@ -4295,10 +4296,6 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
* If there are no triggers in 'trigdesc' that request relevant transition
* tables, then return NULL.
*
- * The resulting object can be passed to the ExecAR* functions. The caller
- * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing
- * with child tables.
- *
* Note that we copy the flags from a parent table into this struct (rather
* than subsequently using the relation's TriggerDesc directly) so that we can
* use it to control collection of transition tuples from child tables.
@@ -5392,7 +5389,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = transition_capture->tcs_map;
+ PartitionRoutingInfo *pinfo = relinfo->ri_PartitionInfo;
+ TupleConversionMap *map = pinfo ? pinfo->pi_PartitionToRootMap :
+ relinfo->ri_childToRootMap;
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 166486d..5f1b560 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,35 +828,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_plannedstmt = plannedstmt;
/*
- * Initialize ResultRelInfo data structures, and open the result rels.
+ * Allocate space for ResultRelInfo pointers that will be filled later.
+ * See ExecGetResultRelInfo() and ExecGetRootResultRelInfo().
*/
if (plannedstmt->resultRelations)
{
List *resultRelations = plannedstmt->resultRelations;
int numResultRelations = list_length(resultRelations);
- ResultRelInfo *resultRelInfos;
- ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, resultRelations)
- {
- Index resultRelationIndex = lfirst_int(l);
- Relation resultRelation;
-
- resultRelation = ExecGetRangeTableRelation(estate,
- resultRelationIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelation,
- resultRelationIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
estate->es_num_result_relations = numResultRelations;
-
/* es_result_relation_info is NULL except when within ModifyTable */
estate->es_result_relation_info = NULL;
@@ -869,25 +851,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
{
int num_roots = list_length(plannedstmt->rootResultRelations);
- resultRelInfos = (ResultRelInfo *)
- palloc(num_roots * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, plannedstmt->rootResultRelations)
- {
- Index resultRelIndex = lfirst_int(l);
- Relation resultRelDesc;
-
- resultRelDesc = ExecGetRangeTableRelation(estate,
- resultRelIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelDesc,
- resultRelIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
-
- estate->es_root_result_relations = resultRelInfos;
+ estate->es_root_result_relations =
+ palloc0(num_roots * sizeof(ResultRelInfo *));
estate->es_num_root_result_relations = num_roots;
}
else
@@ -1376,24 +1341,18 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
MemoryContext oldcontext;
/* First, search through the query result relations */
- rInfo = estate->es_result_relations;
- nr = estate->es_num_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/* Second, search through the root result relations, if any */
- rInfo = estate->es_root_result_relations;
- nr = estate->es_num_root_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_root_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_root_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/*
@@ -1559,14 +1518,21 @@ ExecEndPlan(PlanState *planstate, EState *estate)
ExecResetTupleTable(estate->es_tupleTable, false);
/*
- * close indexes of result relation(s) if any. (Rels themselves get
- * closed next.)
+ * Close indexes of result relation(s) if any. (Rels themselves get
+ * closed next.) Also, allow the result relation's FDWs to shut down.
*/
- resultRelInfo = estate->es_result_relations;
- for (i = estate->es_num_result_relations; i > 0; i--)
+ for (i = 0; i < estate->es_num_result_relations; i++)
{
- ExecCloseIndices(resultRelInfo);
- resultRelInfo++;
+ resultRelInfo = estate->es_result_relations[i];
+ if (resultRelInfo)
+ {
+ ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
+ }
}
/*
@@ -2795,23 +2761,42 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree)
{
int numResultRelations = parentestate->es_num_result_relations;
int numRootResultRels = parentestate->es_num_root_result_relations;
- ResultRelInfo *resultRelInfos;
+ int i;
+ ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_result_relations,
- numResultRelations * sizeof(ResultRelInfo));
- rcestate->es_result_relations = resultRelInfos;
+ rcestate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
+ for (i = 0; i < numResultRelations; i++)
+ {
+ if (parentestate->es_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_result_relations = numResultRelations;
/* Also transfer partitioned root result relations. */
if (numRootResultRels > 0)
{
- resultRelInfos = (ResultRelInfo *)
- palloc(numRootResultRels * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_root_result_relations,
- numRootResultRels * sizeof(ResultRelInfo));
- rcestate->es_root_result_relations = resultRelInfos;
+ rcestate->es_root_result_relations =
+ palloc0(numRootResultRels * sizeof(ResultRelInfo *));
+ for (i = 0; i < numRootResultRels; i++)
+ {
+ if (parentestate->es_root_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_root_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_root_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_root_result_relations = numRootResultRels;
}
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 24afcb2..b425859 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -447,7 +447,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Index firstVarno = node ? linitial_int(node->resultRelations) : 0;
+ Relation firstResultRel = firstVarno > 0 ?
+ ExecGetRangeTableRelation(estate, firstVarno) : NULL;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -495,7 +497,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -559,7 +560,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -618,7 +618,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -864,9 +863,22 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
if (mtstate &&
(mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
{
- partrouteinfo->pi_PartitionToRootMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
- RelationGetDescr(partRelInfo->ri_PartitionRoot));
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+
+ /*
+ * If the partition appears to be an UPDATE result relation, the map
+ * would already have been initialized by ExecBuildResultRelInfo(); use
+ * that one instead of building one from scratch. To distinguish
+ * UPDATE result relations from tuple-routing result relations, we rely
+ * on the fact that each of the former has a distinct RT index.
+ */
+ if (node && node->rootRelation != partRelInfo->ri_RangeTableIndex)
+ partrouteinfo->pi_PartitionToRootMap =
+ partRelInfo->ri_childToRootMap;
+ else
+ partrouteinfo->pi_PartitionToRootMap =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
+ RelationGetDescr(partRelInfo->ri_PartitionRoot));
}
else
partrouteinfo->pi_PartitionToRootMap = NULL;
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d0e65b8..a67c023 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -711,13 +711,13 @@ ExecCreateScanSlotFromOuterPlan(EState *estate,
bool
ExecRelationIsTargetRelation(EState *estate, Index scanrelid)
{
- ResultRelInfo *resultRelInfos;
+ ResultRelInfo **resultRelInfos;
int i;
resultRelInfos = estate->es_result_relations;
for (i = 0; i < estate->es_num_result_relations; i++)
{
- if (resultRelInfos[i].ri_RangeTableIndex == scanrelid)
+ if (resultRelInfos[i]->ri_RangeTableIndex == scanrelid)
return true;
}
return false;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 74c12be..3f611a7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,11 +72,10 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
-static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
-static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
- int whichplan);
static TupleTableSlot *ExecGetNewInsertTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot);
+static ResultRelInfo *ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int resultRelIndex);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1060,6 +1059,9 @@ typedef struct SubplanResultRelHashElem
static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *lc;
HASHCTL ctl;
HTAB *htab;
int i;
@@ -1075,18 +1077,19 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate)
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
mtstate->mt_subplan_resultrel_hash = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nrels; i++)
+ /* Map result relation OIDs to their index in es_result_relations. */
+ i = 0;
+ foreach(lc, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(lc);
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
+ Oid partoid = exec_rt_fetch(rti, estate)->relid;
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->whichrel = i;
+ elem->whichrel = i++;
}
}
@@ -1105,6 +1108,7 @@ ResultRelInfo *
ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
Oid reloid, int *whichrel)
{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
SubplanResultRelHashElem *elem;
Assert(mtstate->mt_subplan_resultrel_hash != NULL);
@@ -1115,13 +1119,416 @@ ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
if (elem)
{
*whichrel = elem->whichrel;
- return mtstate->resultRelInfo + elem->whichrel;
+ return ExecGetResultRelInfo(mtstate,
+ node->resultRelIndex + elem->whichrel,
+ true);
}
return NULL;
}
/*
+ * ExecGetResultRelInfo
+ * Returns the result relation at a given offset within the global array
+ * of ResultRelInfos
+ *
+ * If not present and 'create_it' is true, it is created and put at the given
+ * offset for subsequent calls to find.
+ *
+ * This, in conjunction with ExecLookupResultRelByOid, allows lazy
+ * initialization of ResultRelInfos. That can be helpful in the case where
+ * there are multiple result relations due to inheritance but only one or
+ * few actually end up actually having any tuples to update.
+ *
+ * Note: only call from the executor proper or anything that possesses a valid
+ * execution context, that is an EState with a PlannedStmt, because this
+ * depends on finding a valid PlannedStmt to get result relation RT indexes
+ * from.
+ */
+ResultRelInfo *
+ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[resultRelIndex];
+
+ if (resultRelInfo == NULL && create_it)
+ {
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
+ Index rti = list_nth_int(resultRelations, resultRelIndex);
+
+ resultRelInfo = ExecBuildResultRelInfo(mtstate, rti, resultRelIndex);
+ estate->es_result_relations[resultRelIndex] = resultRelInfo;
+ }
+
+ return resultRelInfo;
+}
+
+/*
+ * ExecGetRootResultRelInfo
+ * Like ExecGetResultRelInfo, but for "root" result relations
+ * corresponding to partitioned tables, which are managed separately from
+ * leaf result relations
+ *
+ * Root ResultRelInfos are never created lazily, although it seems better to
+ * have the same interface to avoid exposing ExecBuildResultRelInfo().
+ */
+ResultRelInfo *
+ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *rootRelInfo = estate->es_root_result_relations[rootRelIndex];
+
+ if (rootRelInfo == NULL)
+ {
+ List *rootRelations = estate->es_plannedstmt->rootResultRelations;
+ Index rti = list_nth_int(rootRelations, rootRelIndex);
+
+ rootRelInfo = ExecBuildResultRelInfo(mtstate, rti, rootRelIndex);
+ estate->es_root_result_relations[rootRelIndex] = rootRelInfo;
+ }
+
+ return rootRelInfo;
+}
+
+/*
+ * ExecBuildResultRelInfo
+ * Builds a ResultRelInfo for a result relation with given RT index
+ */
+static ResultRelInfo *
+ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int resultRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ Plan *subplan = linitial(node->plans);
+ CmdType operation = node->operation;
+ int firstRelIndex = node->resultRelIndex;
+ int thisRelIndex = resultRelIndex - firstRelIndex;
+ Relation relation = ExecGetRangeTableRelation(estate, rti);
+ ResultRelInfo *resultRelInfo;
+ List *resultTargetList = NIL;
+ bool need_projection = false;
+ bool update_tuple_routing_needed = false;
+ ListCell *l;
+ int eflags = estate->es_top_eflags;
+ MemoryContext oldcxt;
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, relation, rti, NULL,
+ estate->es_instrument);
+
+ /*
+ * If this is the root result relation of an UPDATE/DELETE, we only need
+ * a minimally valid result relation.
+ */
+ if (rti == node->rootRelation && operation != CMD_INSERT)
+ {
+ MemoryContextSwitchTo(oldcxt);
+ return resultRelInfo;
+ }
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(thisRelIndex,
+ node->fdwDirectModifyPlans);
+
+ /*
+ * Verify result relation is a valid target for the current operation
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /*
+ * 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);
+
+ /* Result relation specific slot to store the plan's output tuple. */
+ mtstate->mt_scans[thisRelIndex] =
+ ExecInitExtraTupleSlot(mtstate->ps.state, mtstate->mt_plan_tupdesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists,
+ thisRelIndex);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ thisRelIndex,
+ eflags);
+ }
+
+ /*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ if (node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists,
+ thisRelIndex);
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* RETURNING list */
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists,
+ thisRelIndex);
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (node->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (node->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(mtstate->mt_nrels == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a slot
+ * of the table's type here, because the slot will be used to insert
+ * into the table, and for RETURNING processing - which may access
+ * system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(node->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (node->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) node->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Prepare to generate tuples suitable for the target relation.
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ if (operation == CMD_INSERT)
+ {
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (!tle->resjunk)
+ resultTargetList = lappend(resultTargetList, tle);
+ else
+ need_projection = true;
+ }
+ }
+ else
+ {
+ resultTargetList = (List *) list_nth(node->updateTargetLists,
+ thisRelIndex);
+ need_projection = true;
+ }
+
+ /*
+ * The clean list must produce a tuple suitable for the result
+ * relation.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, resultTargetList);
+ }
+
+ if (need_projection)
+ {
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ /*
+ * For UPDATE, we use the old tuple to fill up missing values in
+ * the tuple produced by the plan to get the new tuple.
+ */
+ if (operation == CMD_UPDATE)
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildProjectionInfo(resultTargetList,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps,
+ relDesc);
+ }
+
+ /*
+ * For UPDATE/DELETE, find the appropriate junk attr now.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+ /* HACK: we require it to be present for updates. */
+ if (mtstate->operation == CMD_UPDATE &&
+ !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ else
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ /*
+ * For UPDATE on a partitioned tables, tuple routing might be needed if
+ * if the plan says so or a BEFORE UPDATE trigger is present on the
+ * partition which might modify the partition-key values.
+ */
+ if (mtstate->rootResultRelInfo &&
+ operation == CMD_UPDATE &&
+ (node->partColsUpdated ||
+ (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_update_before_row)))
+ update_tuple_routing_needed = true;
+
+ /* Tuple routing state may already have been initialized. */
+ if (update_tuple_routing_needed &&
+ mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rootRel);
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
+ }
+
+ /*
+ * 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.
+ */
+ if (update_tuple_routing_needed ||
+ (mtstate->mt_transition_capture &&
+ mtstate->operation != CMD_INSERT &&
+ thisRelIndex > 0))
+ {
+ Relation targetRel = getTargetResultRelInfo(mtstate)->ri_RelationDesc;
+
+ resultRelInfo->ri_childToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return resultRelInfo;
+}
+
+/*
* ExecGetInsertNewTuple
* This prepares a "new" tuple ready to be put into a result relation
* by removing any junk columns of the tuple produced by a plan.
@@ -1217,7 +1624,6 @@ ExecUpdate(ModifyTableState *mtstate,
TM_Result result;
TM_FailureData tmfd;
List *recheckIndexes = NIL;
- TupleConversionMap *saved_tcs_map = NULL;
/*
* abort the operation if not running transactions
@@ -1342,7 +1748,6 @@ lreplace:;
TupleTableSlot *ret_slot;
TupleTableSlot *epqslot = NULL;
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
- int map_index;
TupleConversionMap *tupconv_map;
/*
@@ -1413,16 +1818,6 @@ lreplace:;
}
/*
- * Updates set the transition capture map only when a new subplan
- * is chosen. But for inserts, it is set for each row. So after
- * INSERT, we need to revert back to the map created for UPDATE;
- * otherwise the next UPDATE will incorrectly use the one created
- * for INSERT. So first save the one created for UPDATE.
- */
- if (mtstate->mt_transition_capture)
- saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
-
- /*
* resultRelInfo is one of the per-subplan resultRelInfos. So we
* should convert the tuple into root's tuple descriptor, since
* ExecInsert() starts the search from root. The tuple conversion
@@ -1430,9 +1825,7 @@ lreplace:;
* retrieve the one for this resultRel, we need to know the
* position of the resultRel in mtstate->resultRelInfo[].
*/
- map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nrels);
- tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
+ tupconv_map = resultRelInfo->ri_childToRootMap;
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1451,11 +1844,13 @@ lreplace:;
/* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
+
+ /*
+ * Reset the transition state that may possibly have been written
+ * by INSERT.
+ */
if (mtstate->mt_transition_capture)
- {
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map = saved_tcs_map;
- }
return ret_slot;
}
@@ -1921,7 +2316,7 @@ static ResultRelInfo *
getTargetResultRelInfo(ModifyTableState *node)
{
/*
- * Note that if the node modifies a partitioned table, node->resultRelInfo
+ * Note that if the node modifies a partitioned table, plan->resultRelInfo
* points to the first leaf partition, not the root table.
*/
if (node->rootResultRelInfo != NULL)
@@ -1984,28 +2379,6 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
RelationGetRelid(targetRelInfo->ri_RelationDesc),
CMD_UPDATE);
-
- /*
- * If we found that we need to collect transition tuples then we may also
- * need tuple conversion maps for any children that have TupleDescs that
- * aren't compatible with the tuplestores. (We can share these maps
- * between the regular and ON CONFLICT cases.)
- */
- if (mtstate->mt_transition_capture != NULL ||
- mtstate->mt_oc_transition_capture != NULL)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
-
- /*
- * Install the conversion map for the first plan for UPDATE and DELETE
- * operations. It will be advanced each time we switch to the next
- * plan. (INSERT operations set it every time, so we need not update
- * mtstate->mt_oc_transition_capture here.)
- */
- if (mtstate->mt_transition_capture && mtstate->operation != CMD_INSERT)
- mtstate->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(mtstate, 0);
- }
}
/*
@@ -2047,37 +2420,16 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
estate->es_result_relation_info = partrel;
/*
- * If we're capturing transition tuples, we might need to convert from the
- * partition rowtype to root partitioned table's rowtype.
+ * If we're capturing transition tuples and there are no BR triggers to
+ * modify the row, we can simply put the original tuple into the
+ * transition tuplestore.
*/
- if (mtstate->mt_transition_capture != NULL)
- {
- if (partrel->ri_TrigDesc &&
- partrel->ri_TrigDesc->trig_insert_before_row)
- {
- /*
- * If there are any BEFORE triggers on the partition, we'll have
- * to be ready to convert their result back to tuplestore format.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted tuple, to
- * avoid a needless round trip conversion.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
- mtstate->mt_transition_capture->tcs_map = NULL;
- }
- }
- if (mtstate->mt_oc_transition_capture != NULL)
- {
- mtstate->mt_oc_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
+ if (mtstate->mt_transition_capture != NULL &&
+ !(partrel->ri_TrigDesc &&
+ partrel->ri_TrigDesc->trig_insert_before_row))
+ mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
+ else if (mtstate->mt_transition_capture != NULL)
+ mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
/*
* Convert the tuple, if necessary.
@@ -2093,58 +2445,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
return slot;
}
-/*
- * Initialize the child-to-root tuple conversion map array for UPDATE subplans.
- *
- * This map array is required to convert the tuple from the subplan result rel
- * to the target table descriptor. This requirement arises for two independent
- * scenarios:
- * 1. For update-tuple-routing.
- * 2. For capturing tuples in transition tables.
- */
-static void
-ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
-{
- ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
- ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
- TupleDesc outdesc;
- int numResultRelInfos = mtstate->mt_nrels;
- int i;
-
- /*
- * Build array of conversion maps from each child's TupleDesc to the one
- * used in the target relation. The map pointers may be NULL when no
- * conversion is necessary, which is hopefully a common case.
- */
-
- /* Get tuple descriptor of the target rel. */
- outdesc = RelationGetDescr(targetRelInfo->ri_RelationDesc);
-
- mtstate->mt_per_subplan_tupconv_maps = (TupleConversionMap **)
- palloc(sizeof(TupleConversionMap *) * numResultRelInfos);
-
- for (i = 0; i < numResultRelInfos; ++i)
- {
- mtstate->mt_per_subplan_tupconv_maps[i] =
- convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
- outdesc);
- }
-}
-
-/*
- * For a given subplan index, get the tuple conversion map.
- */
-static TupleConversionMap *
-tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
-{
- /* If nobody else set the per-subplan array of maps, do so ourselves. */
- if (mtstate->mt_per_subplan_tupconv_maps == NULL)
- ExecSetupChildParentMapForSubplan(mtstate);
-
- Assert(whichplan >= 0 && whichplan < mtstate->mt_nrels);
- return mtstate->mt_per_subplan_tupconv_maps[whichplan];
-}
-
/* ----------------------------------------------------------------
* ExecModifyTable
*
@@ -2159,8 +2459,10 @@ ExecModifyTable(PlanState *pstate)
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
EState *estate = node->ps.state;
CmdType operation = node->operation;
+ int firstRelIndex = ((ModifyTable *) node->ps.plan)->resultRelIndex;
ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo;
+ int whichrel;
PlanState *subplanstate;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
@@ -2202,7 +2504,8 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichrel;
+ whichrel = 0; /* default result rel */
+ resultRelInfo = ExecGetResultRelInfo(node, firstRelIndex + whichrel, true);
subplanstate = node->mt_plans[node->mt_whichplan];
/*
@@ -2262,31 +2565,18 @@ ExecModifyTable(PlanState *pstate)
/* Table OID -> ResultRelInfo. */
resultRelInfo = ExecLookupModifyResultRelByOid(node, tableoid,
- &node->mt_whichrel);
- Assert(node->mt_whichrel >= 0 &&
- node->mt_whichrel < node->mt_nrels);
+ &whichrel);
+ Assert(whichrel >= 0 && whichrel < node->mt_nrels);
estate->es_result_relation_info = resultRelInfo;
-
- /* Prepare to convert transition tuples from this child. */
- if (node->mt_transition_capture != NULL)
- {
- node->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichrel);
- }
- if (node->mt_oc_transition_capture != NULL)
- {
- node->mt_oc_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichrel);
- }
}
/*
* Ensure input tuple is the right format for the target relation.
*/
- if (node->mt_scans[node->mt_whichrel]->tts_ops != planSlot->tts_ops)
+ if (node->mt_scans[whichrel]->tts_ops != planSlot->tts_ops)
{
- ExecCopySlot(node->mt_scans[node->mt_whichrel], planSlot);
- planSlot = node->mt_scans[node->mt_whichrel];
+ ExecCopySlot(node->mt_scans[whichrel], planSlot);
+ planSlot = node->mt_scans[whichrel];
}
/*
@@ -2450,14 +2740,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
CmdType operation = node->operation;
int nplans = list_length(node->plans);
int nrels = list_length(node->resultRelations);
- ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- TupleDesc plan_result_type;
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)));
@@ -2475,14 +2760,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_done = false;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
- mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nrels);
- /* If modifying a partitioned table, initialize the root table info */
- if (node->rootResultRelIndex >= 0)
- mtstate->rootResultRelInfo = estate->es_root_result_relations +
- node->rootResultRelIndex;
-
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2491,7 +2770,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
mtstate->mt_nrels = nrels;
- mtstate->mt_whichrel = 0;
/*
* Call ExecInitNode on the only plan to be executed and save the result
@@ -2502,12 +2780,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* (see contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify()
* as one example).
*/
- saved_resultRelInfo = estate->es_result_relation_info;
- estate->es_result_relation_info = mtstate->resultRelInfo;
subplan = linitial(node->plans);
mtstate->mt_plans[0] = ExecInitNode(subplan, estate, eflags);
- estate->es_result_relation_info = saved_resultRelInfo;
- plan_result_type = ExecGetResultType(mtstate->mt_plans[0]);
+ mtstate->mt_plan_tupdesc = ExecGetResultType(mtstate->mt_plans[0]);
if (mtstate->mt_nrels > 1)
{
@@ -2518,6 +2793,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_tableOidAttno =
ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
Assert(AttributeNumberIsValid(mtstate->mt_tableOidAttno));
+
+ /*
+ * Also, initialize a hash table to look up UPDATE/DELETE result
+ * relations.
+ */
+ ExecHashSubPlanResultRelsByOid(mtstate);
}
/* Initialize some global state for RETURNING projections. */
@@ -2549,226 +2830,25 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
/*
- * Per result relation initializations.
- * TODO: do this lazily.
+ * If modifying a partitioned table, initialize the root table info.
*/
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nrels; i++)
- {
- List *resultTargetList = NIL;
- bool need_projection = false;
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, plan_result_type,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Now init the plan for this result rel */
- estate->es_result_relation_info = resultRelInfo;
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- i,
- eflags);
- }
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- if (node->withCheckOptionLists)
- {
- List *wcoList = (List *) list_nth(node->withCheckOptionLists, i);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- }
- if (node->returningLists)
- {
- List *rlist = (List *) list_nth(node->returningLists, i);
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- slot = mtstate->ps.ps_ResultTupleSlot;
- Assert(slot != NULL);
- econtext = mtstate->ps.ps_ExprContext;
- Assert(econtext != NULL);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- }
-
- /*
- * Prepare to generate tuples suitable for the target relation.
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- if (operation == CMD_INSERT)
- {
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (!tle->resjunk)
- resultTargetList = lappend(resultTargetList, tle);
- else
- need_projection = true;
- }
- }
- else
- {
- resultTargetList = (List *) list_nth(node->updateTargetLists,
- i);
- need_projection = true;
- }
-
- /*
- * The clean list must produce a tuple suitable for the result
- * relation.
- */
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- resultTargetList);
- }
-
- if (need_projection)
- {
- TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
-
- /*
- * For UPDATE, we use the old tuple to fill up missing values in
- * the tuple produced by the plan to get the new tuple.
- */
- if (operation == CMD_UPDATE)
- resultRelInfo->ri_oldTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
- resultRelInfo->ri_newTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* need an expression context to do the projection */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- resultRelInfo->ri_projectNew =
- ExecBuildProjectionInfo(resultTargetList,
- mtstate->ps.ps_ExprContext,
- resultRelInfo->ri_newTupleSlot,
- &mtstate->ps,
- relDesc);
- }
-
- /*
- * For UPDATE/DELETE, find the appropriate junk attr now.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
- if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist,
- "wholerow");
- /* HACK: we require it to be present for updates. */
- if (mtstate->operation == CMD_UPDATE &&
- !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk wholerow column");
- }
- else
- {
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
- if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo++;
- }
+ if (node->rootResultRelIndex >= 0)
+ mtstate->rootResultRelInfo =
+ ExecGetRootResultRelInfo(mtstate, node->rootResultRelIndex);
+ if (mtstate->rootResultRelInfo == NULL)
+ mtstate->resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex, true);
/* Get the target relation */
rel = (getTargetResultRelInfo(mtstate))->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 an INSERT. If an UPDATE might
+ * need it, ExecBuildResultRelInfo will build it when initializing
+ * a partition's ResultRelInfo.
*/
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);
@@ -2780,83 +2860,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * Construct mapping from each of the per-subplan partition attnos to the
- * root attno. This is required when during update row movement the tuple
- * descriptor of a source partition does not match the root partitioned
- * table descriptor. In such a case we need to convert tuples to the root
- * tuple descriptor, because the search for destination partition starts
- * from the root. We'll also need a slot to store these converted tuples.
- * We can skip this setup if it's not a partition key update.
- */
- if (update_tuple_routing_needed)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
- mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- 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);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
- /*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
* EvalPlanQual mechanism needs to be told about them. Locate the
@@ -2884,13 +2887,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize a hash table to look up UPDATE/DELETE result relations by
- * OID if there is more than one.
- */
- if (mtstate->operation != CMD_INSERT && mtstate->mt_nrels > 1)
- ExecHashSubPlanResultRelsByOid(mtstate);
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2920,20 +2916,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nrels; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index a752a12..42c4fc3 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -213,7 +213,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
resultRelInfo = makeNode(ResultRelInfo);
InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index a40ddf5..d1f436c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -45,10 +45,6 @@ typedef struct TriggerData
/*
* The state for capturing old and new tuples into transition tables for a
* single ModifyTable node (or other operation source, e.g. copy.c).
- *
- * This is per-caller to avoid conflicts in setting tcs_map or
- * tcs_original_insert_tuple. Note, however, that the pointed-to
- * private data may be shared across multiple callers.
*/
struct AfterTriggersTableData; /* private in trigger.c */
@@ -66,14 +62,6 @@ typedef struct TransitionCaptureState
bool tcs_insert_new_table;
/*
- * For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the
- * new and old tuples from a child table's format to the format of the
- * relation named in a query so that it is compatible with the transition
- * tuplestores. The caller must store the conversion map here if so.
- */
- TupleConversionMap *tcs_map;
-
- /*
* For INSERT and COPY, it would be wasteful to convert tuples from child
* format to parent format after they have already been converted in the
* opposite direction during routing. In that case we bypass conversion
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1f4efd6..317e739 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -606,6 +606,10 @@ extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
/* prototypes from functions in nodeModifyTable.c */
extern ResultRelInfo *ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
Oid reloid, int *whichrel);
+extern ResultRelInfo *ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it);
+extern ResultRelInfo *ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex);
+
/* needed by trigger.c */
extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 4ec4ebd..a30f5cf 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -20,5 +20,4 @@ extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, Cmd
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
-
#endif /* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 88ac0b2..a1d2b2b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -494,6 +494,12 @@ typedef struct ResultRelInfo
TupleTableSlot *ri_oldTupleSlot;
TupleTableSlot *ri_newTupleSlot;
ProjectionInfo *ri_projectNew;
+
+ /*
+ * Map to convert child sublan tuples to root parent format, set iff
+ * either update row movement or transition tuple capture is active.
+ */
+ TupleConversionMap *ri_childToRootMap;
} ResultRelInfo;
/* ----------------
@@ -525,7 +531,7 @@ typedef struct EState
CommandId es_output_cid;
/* Info about target table(s) for insert/update/delete queries: */
- ResultRelInfo *es_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_result_relations; /* array of ResultRelInfo pointers */
int es_num_result_relations; /* length of array */
ResultRelInfo *es_result_relation_info; /* currently active array elt */
@@ -535,7 +541,8 @@ typedef struct EState
* es_result_relations, but we need access to the roots for firing
* triggers and for runtime tuple routing.
*/
- ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_root_result_relations; /* array of ResultRelInfo
+ * pointers */
int es_num_root_result_relations; /* length of the array */
PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */
@@ -1173,7 +1180,7 @@ typedef struct ModifyTableState
int mt_nplans; /* number of plans in mt_plans (only 1!) */
int mt_whichplan; /* which one is being executed (always 0th!) */
int mt_nrels; /* number of result rels in the arrays */
- int mt_whichrel; /* Array index of target rel being targeted */
+ TupleDesc mt_plan_tupdesc; /* TupleDesc of plan's output */
TupleTableSlot **mt_scans; /* input tuple for each result relation */
/*
@@ -1182,7 +1189,7 @@ typedef struct ModifyTableState
*/
int mt_tableOidAttno;
- ResultRelInfo *resultRelInfo; /* Target relations */
+ ResultRelInfo *resultRelInfo; /* Target relation */
HTAB *mt_subplan_resultrel_hash; /* hash table to look up result
* relation by OID. */
ResultRelInfo *rootResultRelInfo; /* root target relation (partitioned
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index a295c08..ea3200a 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -1837,6 +1837,53 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
Filter: ((b >= $1) AND (b <= $2) AND (a < $0))
(10 rows)
+-- Runtime pruning for UPDATE/DELETE (mainly notice result relations listed)
+prepare upd_q (int, int) as
+update ab set a = a where a = $1 and b = $2;
+-- PARAM_EXTERN
+explain (analyze, costs off, summary off, timing off) execute upd_q (1, 1);
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on ab (actual rows=0 loops=1)
+ Update on ab_a1_b1 ab_1
+ -> Append (actual rows=0 loops=1)
+ Subplans Removed: 8
+ -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Filter: ((a = $1) AND (b = $2))
+(6 rows)
+
+-- PARAM_EXEC
+explain (analyze, costs off, summary off, timing off)
+update ab set a = a where a = (select 1) and b = (select 1);
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on ab (actual rows=0 loops=1)
+ Update on ab_a1_b1 ab_1
+ InitPlan 1 (returns $0)
+ -> Result (actual rows=1 loops=1)
+ InitPlan 2 (returns $1)
+ -> Result (actual rows=1 loops=1)
+ -> Append (actual rows=0 loops=1)
+ -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a1_b2 ab_2 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a1_b3 ab_3 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b1 ab_4 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b2 ab_5 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b3 ab_6 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b1 ab_7 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b2 ab_8 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b3 ab_9 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+(25 rows)
+
-- Test a backwards Append scan
create table list_part (a int) partition by list (a);
create table list_part1 partition of list_part for values in (1);
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 6658455..e7b617d 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -389,6 +389,18 @@ select a from ab where b between $1 and $2 and a < (select 3);
explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
+-- Runtime pruning for UPDATE/DELETE (mainly notice result relations listed)
+
+prepare upd_q (int, int) as
+update ab set a = a where a = $1 and b = $2;
+
+-- PARAM_EXTERN
+explain (analyze, costs off, summary off, timing off) execute upd_q (1, 1);
+
+-- PARAM_EXEC
+explain (analyze, costs off, summary off, timing off)
+update ab set a = a where a = (select 1) and b = (select 1);
+
-- Test a backwards Append scan
create table list_part (a int) partition by list (a);
create table list_part1 partition of list_part for values in (1);
--
1.8.3.1
v2-0002-Do-not-set-rootResultRelIndex-unnecessarily.patchapplication/octet-stream; name=v2-0002-Do-not-set-rootResultRelIndex-unnecessarily.patchDownload
From f933948b41d89c0b9b35a535bf8c23a9c0eef6f9 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 13:12:21 +0900
Subject: [PATCH v2 2/3] Do not set rootResultRelIndex unnecessarily
It's set in ModifyTable for all INSERT, UPDATE, and DELETE, however
only needed for the latter two operations.
---
src/backend/optimizer/plan/setrefs.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 3eb0fc1..5001966 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -935,8 +935,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
* If the main target relation is a partitioned table, also
* add the partition root's RT index to rootResultRelations,
* and remember its index in that list in rootResultRelIndex.
+ * We do this only for UPDATE/DELETE though, because in only
+ * in their case do we need to process the root relation
+ * separately from other result relations.
*/
- if (splan->rootRelation)
+ if (splan->rootRelation && splan->operation != CMD_INSERT)
{
splan->rootResultRelIndex =
list_length(root->glob->rootResultRelations);
--
1.8.3.1
v2-0001-Revise-how-some-FDW-executor-APIs-obtain-ResultRe.patchapplication/octet-stream; name=v2-0001-Revise-how-some-FDW-executor-APIs-obtain-ResultRe.patchDownload
From e0e60f50bea1cb7228b8d84c88c3aab533e67400 Mon Sep 17 00:00:00 2001
From: Etsuro Fujita <efujita@postgresql.org>
Date: Thu, 8 Aug 2019 21:41:12 +0900
Subject: [PATCH v2 1/3] Revise how some FDW executor APIs obtain ResultRelInfo
This adds a field to ForeignScan node to get the index of the target
foreign table in the global list of query result relations (the
es_result_relations array).
So, BeginDirectModify() uses that index to get the RT index of the
target relation from the global list of target relation RT indexes.
Also, IterateDirectModify() uses that index to get the ResultRelInfo
from es_result_relations.
Amit Langote, Etsuro Fujita
Discussion: https://postgr.es/m/20190718010911.l6xcdv6birtxiei4@alap3.anarazel.de
---
contrib/postgres_fdw/postgres_fdw.c | 34 +++++++++++++++++++++++++++------
doc/src/sgml/fdwhandler.sgml | 5 +++--
src/backend/executor/nodeForeignscan.c | 11 +++++++----
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 2 ++
src/backend/optimizer/plan/setrefs.c | 15 +++++++++++++++
src/include/nodes/plannodes.h | 3 +++
9 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 697ec68..4a495bf 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -218,6 +218,7 @@ typedef struct PgFdwDirectModifyState
int num_tuples; /* # of result tuples */
int next_tuple; /* index of next one to return */
Relation resultRel; /* relcache entry for the target relation */
+ ResultRelInfo *resultRelInfo; /* ResultRelInfo for the target relation */
AttrNumber *attnoMap; /* array of attnums of input user columns */
AttrNumber ctidAttno; /* attnum of input ctid column */
AttrNumber oidAttno; /* attnum of input oid column */
@@ -361,7 +362,8 @@ static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
-static void postgresBeginDirectModify(ForeignScanState *node, int eflags);
+static void postgresBeginDirectModify(ForeignScanState *node,
+ int eflags);
static TupleTableSlot *postgresIterateDirectModify(ForeignScanState *node);
static void postgresEndDirectModify(ForeignScanState *node);
static void postgresExplainForeignScan(ForeignScanState *node,
@@ -2321,6 +2323,11 @@ postgresPlanDirectModify(PlannerInfo *root,
rebuild_fdw_scan_tlist(fscan, returningList);
}
+ /*
+ * Set the index of the subplan result rel.
+ */
+ fscan->resultRelIndex = subplan_index;
+
table_close(rel, NoLock);
return true;
}
@@ -2330,10 +2337,12 @@ postgresPlanDirectModify(PlannerInfo *root,
* Prepare a direct foreign table modification
*/
static void
-postgresBeginDirectModify(ForeignScanState *node, int eflags)
+postgresBeginDirectModify(ForeignScanState *node,
+ int eflags)
{
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
PgFdwDirectModifyState *dmstate;
Index rtindex;
RangeTblEntry *rte;
@@ -2358,7 +2367,8 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does.
*/
- rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
+ Assert(fsplan->resultRelIndex >= 0);
+ rtindex = list_nth_int(resultRelations, fsplan->resultRelIndex);
rte = exec_rt_fetch(rtindex, estate);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
@@ -2391,6 +2401,9 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
dmstate->rel = NULL;
}
+ /* ResultRelInfo is set when first needed by IterateDirectModify(). */
+ dmstate->resultRelInfo = NULL;
+
/* Initialize state variable */
dmstate->num_tuples = -1; /* -1 means not set yet */
@@ -2453,7 +2466,16 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = dmstate->resultRelInfo;
+
+ if (resultRelInfo == NULL)
+ {
+ ForeignScan *fscan = (ForeignScan *) node->ss.ps.plan;
+
+ resultRelInfo = &estate->es_result_relations[fscan->resultRelIndex];
+ Assert(resultRelInfo != NULL);
+ dmstate->resultRelInfo = resultRelInfo;
+ }
/*
* If this is the first call after Begin, execute the statement.
@@ -4089,7 +4111,7 @@ get_returning_data(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = dmstate->resultRelInfo;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
TupleTableSlot *resultSlot;
@@ -4236,7 +4258,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
TupleTableSlot *slot,
EState *estate)
{
- ResultRelInfo *relInfo = estate->es_result_relation_info;
+ ResultRelInfo *relInfo = dmstate->resultRelInfo;
TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
TupleTableSlot *resultSlot;
Datum *values;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 6587678..7439cd1 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -917,8 +917,9 @@ IterateDirectModify(ForeignScanState *node);
tuple table slot (the node's <structfield>ScanTupleSlot</structfield> should be
used for this purpose). The data that was actually inserted, updated
or deleted must be stored in the
- <literal>es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
- of the node's <structname>EState</structname>.
+ <literal>ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
+ of the target foreign table's <structname>ResultRelInfo</structname>
+ passed to <function>BeginDirectModify</function>.
Return NULL if no more rows are available.
Note that this is called in a short-lived memory context that will be
reset between invocations. Create a memory context in
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 513471a..ec5f166 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -221,12 +221,15 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan or the direct modification.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
+ else
+ {
+ Assert(node->resultRelIndex >= 0);
+ fdwroutine->BeginDirectModify(scanstate, eflags);
+ }
return scanstate;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c988c96..4d4b434 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -762,6 +762,7 @@ _copyForeignScan(const ForeignScan *from)
COPY_NODE_FIELD(fdw_recheck_quals);
COPY_BITMAPSET_FIELD(fs_relids);
COPY_SCALAR_FIELD(fsSystemCol);
+ COPY_SCALAR_FIELD(resultRelIndex);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d168ecc..0997e74 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -699,6 +699,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
WRITE_NODE_FIELD(fdw_recheck_quals);
WRITE_BITMAPSET_FIELD(fs_relids);
WRITE_BOOL_FIELD(fsSystemCol);
+ WRITE_INT_FIELD(resultRelIndex);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 92bb7ad..872cf10 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2018,6 +2018,7 @@ _readForeignScan(void)
READ_NODE_FIELD(fdw_recheck_quals);
READ_BITMAPSET_FIELD(fs_relids);
READ_BOOL_FIELD(fsSystemCol);
+ READ_INT_FIELD(resultRelIndex);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index da4a804..1078704 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -5557,6 +5557,8 @@ make_foreignscan(List *qptlist,
node->fs_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;
+ /* resultRelIndex will be set by PlanDirectModify/setrefs.c, if needed */
+ node->resultRelIndex = -1;
return node;
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6a52c6c..3eb0fc1 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -906,6 +906,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
rc->rti += rtoffset;
rc->prti += rtoffset;
}
+ /*
+ * Caution: Do not change the relative ordering of this loop
+ * and the statement below that adds the result relations to
+ * root->glob->resultRelations, because we need to use the
+ * current value of list_length(root->glob->resultRelations)
+ * in some plans.
+ */
foreach(l, splan->plans)
{
lfirst(l) = set_plan_refs(root,
@@ -1245,6 +1252,14 @@ set_foreignscan_references(PlannerInfo *root,
}
fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
+
+ /*
+ * Adjust resultRelIndex if it's valid (note that we are called before
+ * adding the RT indexes of ModifyTable result relations to the global
+ * list)
+ */
+ if (fscan->resultRelIndex >= 0)
+ fscan->resultRelIndex += list_length(root->glob->resultRelations);
}
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 909820b..ffe1830 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -621,6 +621,9 @@ typedef struct ForeignScan
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
Bitmapset *fs_relids; /* RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
+ int resultRelIndex; /* index of foreign table in the list of query
+ * result relations for INSERT/UPDATE/DELETE;
+ * -1 for SELECT */
} ForeignScan;
/* ----------------
--
1.8.3.1
On 1 Jul 2020, at 08:30, Amit Langote <amitlangote09@gmail.com> wrote:
On Fri, Jun 26, 2020 at 9:36 PM Amit Langote <amitlangote09@gmail.com> wrote:
I would like to discuss a refactoring patch that builds on top of the
patches at [1] to address $subject.I forgot to update a place in postgres_fdw causing one of its tests to crash.
Fixed in the attached updated version.
The attached 0003 fails to apply to current HEAD, please submit another rebased
version. Marking the entry as Waiting on Author in the meantime.
cheers ./daniel
Hi Daniel,
On Wed, Jul 1, 2020 at 6:50 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 1 Jul 2020, at 08:30, Amit Langote <amitlangote09@gmail.com> wrote:
On Fri, Jun 26, 2020 at 9:36 PM Amit Langote <amitlangote09@gmail.com> wrote:
I would like to discuss a refactoring patch that builds on top of the
patches at [1] to address $subject.I forgot to update a place in postgres_fdw causing one of its tests to crash.
Fixed in the attached updated version.
The attached 0003 fails to apply to current HEAD, please submit another rebased
version. Marking the entry as Waiting on Author in the meantime.
Thank you for the heads up.
Actually, as I noted in the first email, the patches here are to be
applied on top of patches of another thread that I chose not to post
here. But I can see how that is inconvenient both for the CF bot and
other humans, so I'm attaching all of the patches.
Another thing I could do is decouple the patches to discuss here from
the patches of the other thread, which should be possible and might be
good to avoid back and forth between the two threads.
--
Amit Langote
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v2-0005-Delay-initializing-UPDATE-DELETE-ResultRelInfos.patchapplication/octet-stream; name=v2-0005-Delay-initializing-UPDATE-DELETE-ResultRelInfos.patchDownload
From 85744a49e71cd48c30cc460b951b996a0251ddd4 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 19:41:49 +0900
Subject: [PATCH v2 5/8] Delay initializing UPDATE/DELETE ResultRelInfos
Currently, InitPlan() makes one for each of the result relations in
PlannedStmt.resultRelations which may be a long list if the planner
couldn't perform any pruning, such as when making a generic plan.
Instead, make the ResultRelInfo of a given result relation only when
the plan actually produces a tuple to update/delete that belongs to
that relation.
As part of this, the tuple conversion map to convert from child
format to root format that used to be part of TransitionCaptureState
is now moved into ResultRelInfo so that it can also be initialized
lazily for a given child result relation along with its
ResultRelInfo.
---
contrib/postgres_fdw/postgres_fdw.c | 4 +-
src/backend/commands/copy.c | 37 +-
src/backend/commands/explain.c | 41 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/commands/trigger.c | 9 +-
src/backend/executor/execMain.c | 125 ++--
src/backend/executor/execPartition.c | 26 +-
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 936 +++++++++++++-------------
src/backend/replication/logical/worker.c | 2 +-
src/include/commands/trigger.h | 12 -
src/include/executor/executor.h | 4 +
src/include/executor/nodeModifyTable.h | 1 -
src/include/nodes/execnodes.h | 15 +-
src/test/regress/expected/partition_prune.out | 47 ++
src/test/regress/sql/partition_prune.sql | 12 +
16 files changed, 659 insertions(+), 618 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 4a495bf..f606dee 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1994,7 +1994,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
*/
if (plan && plan->operation == CMD_UPDATE &&
resultRelation == plan->rootRelation)
- resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+ resultRelation = linitial_int(plan->resultRelations);
}
/* Construct the SQL command string. */
@@ -2472,7 +2472,7 @@ postgresIterateDirectModify(ForeignScanState *node)
{
ForeignScan *fscan = (ForeignScan *) node->ss.ps.plan;
- resultRelInfo = &estate->es_result_relations[fscan->resultRelIndex];
+ resultRelInfo = estate->es_result_relations[fscan->resultRelIndex];
Assert(resultRelInfo != NULL);
dmstate->resultRelInfo = resultRelInfo;
}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3e199bd..581d28f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2784,7 +2784,7 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
@@ -2798,7 +2798,7 @@ CopyFrom(CopyState cstate)
mtstate->ps.plan = NULL;
mtstate->ps.state = estate;
mtstate->operation = CMD_INSERT;
- mtstate->resultRelInfo = estate->es_result_relations;
+ mtstate->resultRelInfo = resultRelInfo;
if (resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
@@ -3063,32 +3063,15 @@ CopyFrom(CopyState cstate)
estate->es_result_relation_info = resultRelInfo;
/*
- * If we're capturing transition tuples, we might need to convert
- * from the partition rowtype to root rowtype.
+ * If we're capturing transition tuples and there are no BR
+ * triggers to modify the row, we can simply put the original
+ * tuple into the transition tuplestore.
*/
- if (cstate->transition_capture != NULL)
- {
- if (has_before_insert_row_trig)
- {
- /*
- * If there are any BEFORE triggers on the partition,
- * we'll have to be ready to convert their result back to
- * tuplestore format.
- */
- cstate->transition_capture->tcs_original_insert_tuple = NULL;
- cstate->transition_capture->tcs_map =
- resultRelInfo->ri_PartitionInfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted
- * tuple, to avoid a needless round trip conversion.
- */
- cstate->transition_capture->tcs_original_insert_tuple = myslot;
- cstate->transition_capture->tcs_map = NULL;
- }
- }
+ if (cstate->transition_capture != NULL &&
+ !has_before_insert_row_trig)
+ cstate->transition_capture->tcs_original_insert_tuple = myslot;
+ else if (cstate->transition_capture != NULL)
+ cstate->transition_capture->tcs_original_insert_tuple = NULL;
/*
* We might need to convert from the root rowtype to the partition
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 37e7ae1..17a4baa 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -795,13 +796,21 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
show_relname = (numrels > 1 || numrootrels > 0 ||
routerels != NIL || targrels != NIL);
- rInfo = queryDesc->estate->es_result_relations;
- for (nr = 0; nr < numrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ for (nr = 0; nr < numrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_result_relations[nr];
- rInfo = queryDesc->estate->es_root_result_relations;
- for (nr = 0; nr < numrootrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
+
+ for (nr = 0; nr < numrootrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_root_result_relations[nr];
+
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
foreach(l, routerels)
{
@@ -3669,15 +3678,29 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nrels > 1 ||
(mtstate->mt_nrels == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ linitial_int(node->resultRelations) != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nrels; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
- FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
+ /*
+ * Get the ResultRelInfo of to show the information of this result
+ * relation. When the ModifyTable is actually performed (ANALYZE is
+ * on), we pass false for create_it, so as to show only those that
+ * were actually initialized during the execution due to some tuple
+ * in them getting modified.
+ */
+ ResultRelInfo *resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex + j,
+ !es->analyze);
+ FdwRoutine *fdwroutine;
+
+ if (resultRelInfo == NULL)
+ continue;
+
+ fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f..0342429 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1789,7 +1789,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
0);
resultRelInfo++;
}
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations = &resultRelInfos;
estate->es_num_result_relations = list_length(rels);
/*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 58a5111..98c83a2 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -34,6 +34,7 @@
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
+#include "executor/execPartition.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
@@ -4295,10 +4296,6 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
* If there are no triggers in 'trigdesc' that request relevant transition
* tables, then return NULL.
*
- * The resulting object can be passed to the ExecAR* functions. The caller
- * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing
- * with child tables.
- *
* Note that we copy the flags from a parent table into this struct (rather
* than subsequently using the relation's TriggerDesc directly) so that we can
* use it to control collection of transition tuples from child tables.
@@ -5392,7 +5389,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = transition_capture->tcs_map;
+ PartitionRoutingInfo *pinfo = relinfo->ri_PartitionInfo;
+ TupleConversionMap *map = pinfo ? pinfo->pi_PartitionToRootMap :
+ relinfo->ri_childToRootMap;
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 166486d..5f1b560 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,35 +828,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_plannedstmt = plannedstmt;
/*
- * Initialize ResultRelInfo data structures, and open the result rels.
+ * Allocate space for ResultRelInfo pointers that will be filled later.
+ * See ExecGetResultRelInfo() and ExecGetRootResultRelInfo().
*/
if (plannedstmt->resultRelations)
{
List *resultRelations = plannedstmt->resultRelations;
int numResultRelations = list_length(resultRelations);
- ResultRelInfo *resultRelInfos;
- ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, resultRelations)
- {
- Index resultRelationIndex = lfirst_int(l);
- Relation resultRelation;
-
- resultRelation = ExecGetRangeTableRelation(estate,
- resultRelationIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelation,
- resultRelationIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
estate->es_num_result_relations = numResultRelations;
-
/* es_result_relation_info is NULL except when within ModifyTable */
estate->es_result_relation_info = NULL;
@@ -869,25 +851,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
{
int num_roots = list_length(plannedstmt->rootResultRelations);
- resultRelInfos = (ResultRelInfo *)
- palloc(num_roots * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, plannedstmt->rootResultRelations)
- {
- Index resultRelIndex = lfirst_int(l);
- Relation resultRelDesc;
-
- resultRelDesc = ExecGetRangeTableRelation(estate,
- resultRelIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelDesc,
- resultRelIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
-
- estate->es_root_result_relations = resultRelInfos;
+ estate->es_root_result_relations =
+ palloc0(num_roots * sizeof(ResultRelInfo *));
estate->es_num_root_result_relations = num_roots;
}
else
@@ -1376,24 +1341,18 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
MemoryContext oldcontext;
/* First, search through the query result relations */
- rInfo = estate->es_result_relations;
- nr = estate->es_num_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/* Second, search through the root result relations, if any */
- rInfo = estate->es_root_result_relations;
- nr = estate->es_num_root_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_root_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_root_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/*
@@ -1559,14 +1518,21 @@ ExecEndPlan(PlanState *planstate, EState *estate)
ExecResetTupleTable(estate->es_tupleTable, false);
/*
- * close indexes of result relation(s) if any. (Rels themselves get
- * closed next.)
+ * Close indexes of result relation(s) if any. (Rels themselves get
+ * closed next.) Also, allow the result relation's FDWs to shut down.
*/
- resultRelInfo = estate->es_result_relations;
- for (i = estate->es_num_result_relations; i > 0; i--)
+ for (i = 0; i < estate->es_num_result_relations; i++)
{
- ExecCloseIndices(resultRelInfo);
- resultRelInfo++;
+ resultRelInfo = estate->es_result_relations[i];
+ if (resultRelInfo)
+ {
+ ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
+ }
}
/*
@@ -2795,23 +2761,42 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree)
{
int numResultRelations = parentestate->es_num_result_relations;
int numRootResultRels = parentestate->es_num_root_result_relations;
- ResultRelInfo *resultRelInfos;
+ int i;
+ ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_result_relations,
- numResultRelations * sizeof(ResultRelInfo));
- rcestate->es_result_relations = resultRelInfos;
+ rcestate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
+ for (i = 0; i < numResultRelations; i++)
+ {
+ if (parentestate->es_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_result_relations = numResultRelations;
/* Also transfer partitioned root result relations. */
if (numRootResultRels > 0)
{
- resultRelInfos = (ResultRelInfo *)
- palloc(numRootResultRels * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_root_result_relations,
- numRootResultRels * sizeof(ResultRelInfo));
- rcestate->es_root_result_relations = resultRelInfos;
+ rcestate->es_root_result_relations =
+ palloc0(numRootResultRels * sizeof(ResultRelInfo *));
+ for (i = 0; i < numRootResultRels; i++)
+ {
+ if (parentestate->es_root_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_root_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_root_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_root_result_relations = numRootResultRels;
}
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 24afcb2..b425859 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -447,7 +447,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Index firstVarno = node ? linitial_int(node->resultRelations) : 0;
+ Relation firstResultRel = firstVarno > 0 ?
+ ExecGetRangeTableRelation(estate, firstVarno) : NULL;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -495,7 +497,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -559,7 +560,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -618,7 +618,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -864,9 +863,22 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
if (mtstate &&
(mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
{
- partrouteinfo->pi_PartitionToRootMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
- RelationGetDescr(partRelInfo->ri_PartitionRoot));
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+
+ /*
+ * If the partition appears to be an UPDATE result relation, the map
+ * would already have been initialized by ExecBuildResultRelInfo(); use
+ * that one instead of building one from scratch. To distinguish
+ * UPDATE result relations from tuple-routing result relations, we rely
+ * on the fact that each of the former has a distinct RT index.
+ */
+ if (node && node->rootRelation != partRelInfo->ri_RangeTableIndex)
+ partrouteinfo->pi_PartitionToRootMap =
+ partRelInfo->ri_childToRootMap;
+ else
+ partrouteinfo->pi_PartitionToRootMap =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
+ RelationGetDescr(partRelInfo->ri_PartitionRoot));
}
else
partrouteinfo->pi_PartitionToRootMap = NULL;
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d0e65b8..a67c023 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -711,13 +711,13 @@ ExecCreateScanSlotFromOuterPlan(EState *estate,
bool
ExecRelationIsTargetRelation(EState *estate, Index scanrelid)
{
- ResultRelInfo *resultRelInfos;
+ ResultRelInfo **resultRelInfos;
int i;
resultRelInfos = estate->es_result_relations;
for (i = 0; i < estate->es_num_result_relations; i++)
{
- if (resultRelInfos[i].ri_RangeTableIndex == scanrelid)
+ if (resultRelInfos[i]->ri_RangeTableIndex == scanrelid)
return true;
}
return false;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 74c12be..3f611a7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,11 +72,10 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
-static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
-static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
- int whichplan);
static TupleTableSlot *ExecGetNewInsertTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot);
+static ResultRelInfo *ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int resultRelIndex);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1060,6 +1059,9 @@ typedef struct SubplanResultRelHashElem
static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *lc;
HASHCTL ctl;
HTAB *htab;
int i;
@@ -1075,18 +1077,19 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate)
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
mtstate->mt_subplan_resultrel_hash = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nrels; i++)
+ /* Map result relation OIDs to their index in es_result_relations. */
+ i = 0;
+ foreach(lc, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(lc);
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
+ Oid partoid = exec_rt_fetch(rti, estate)->relid;
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->whichrel = i;
+ elem->whichrel = i++;
}
}
@@ -1105,6 +1108,7 @@ ResultRelInfo *
ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
Oid reloid, int *whichrel)
{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
SubplanResultRelHashElem *elem;
Assert(mtstate->mt_subplan_resultrel_hash != NULL);
@@ -1115,13 +1119,416 @@ ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
if (elem)
{
*whichrel = elem->whichrel;
- return mtstate->resultRelInfo + elem->whichrel;
+ return ExecGetResultRelInfo(mtstate,
+ node->resultRelIndex + elem->whichrel,
+ true);
}
return NULL;
}
/*
+ * ExecGetResultRelInfo
+ * Returns the result relation at a given offset within the global array
+ * of ResultRelInfos
+ *
+ * If not present and 'create_it' is true, it is created and put at the given
+ * offset for subsequent calls to find.
+ *
+ * This, in conjunction with ExecLookupResultRelByOid, allows lazy
+ * initialization of ResultRelInfos. That can be helpful in the case where
+ * there are multiple result relations due to inheritance but only one or
+ * few actually end up actually having any tuples to update.
+ *
+ * Note: only call from the executor proper or anything that possesses a valid
+ * execution context, that is an EState with a PlannedStmt, because this
+ * depends on finding a valid PlannedStmt to get result relation RT indexes
+ * from.
+ */
+ResultRelInfo *
+ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[resultRelIndex];
+
+ if (resultRelInfo == NULL && create_it)
+ {
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
+ Index rti = list_nth_int(resultRelations, resultRelIndex);
+
+ resultRelInfo = ExecBuildResultRelInfo(mtstate, rti, resultRelIndex);
+ estate->es_result_relations[resultRelIndex] = resultRelInfo;
+ }
+
+ return resultRelInfo;
+}
+
+/*
+ * ExecGetRootResultRelInfo
+ * Like ExecGetResultRelInfo, but for "root" result relations
+ * corresponding to partitioned tables, which are managed separately from
+ * leaf result relations
+ *
+ * Root ResultRelInfos are never created lazily, although it seems better to
+ * have the same interface to avoid exposing ExecBuildResultRelInfo().
+ */
+ResultRelInfo *
+ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *rootRelInfo = estate->es_root_result_relations[rootRelIndex];
+
+ if (rootRelInfo == NULL)
+ {
+ List *rootRelations = estate->es_plannedstmt->rootResultRelations;
+ Index rti = list_nth_int(rootRelations, rootRelIndex);
+
+ rootRelInfo = ExecBuildResultRelInfo(mtstate, rti, rootRelIndex);
+ estate->es_root_result_relations[rootRelIndex] = rootRelInfo;
+ }
+
+ return rootRelInfo;
+}
+
+/*
+ * ExecBuildResultRelInfo
+ * Builds a ResultRelInfo for a result relation with given RT index
+ */
+static ResultRelInfo *
+ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int resultRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ Plan *subplan = linitial(node->plans);
+ CmdType operation = node->operation;
+ int firstRelIndex = node->resultRelIndex;
+ int thisRelIndex = resultRelIndex - firstRelIndex;
+ Relation relation = ExecGetRangeTableRelation(estate, rti);
+ ResultRelInfo *resultRelInfo;
+ List *resultTargetList = NIL;
+ bool need_projection = false;
+ bool update_tuple_routing_needed = false;
+ ListCell *l;
+ int eflags = estate->es_top_eflags;
+ MemoryContext oldcxt;
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, relation, rti, NULL,
+ estate->es_instrument);
+
+ /*
+ * If this is the root result relation of an UPDATE/DELETE, we only need
+ * a minimally valid result relation.
+ */
+ if (rti == node->rootRelation && operation != CMD_INSERT)
+ {
+ MemoryContextSwitchTo(oldcxt);
+ return resultRelInfo;
+ }
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(thisRelIndex,
+ node->fdwDirectModifyPlans);
+
+ /*
+ * Verify result relation is a valid target for the current operation
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /*
+ * 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);
+
+ /* Result relation specific slot to store the plan's output tuple. */
+ mtstate->mt_scans[thisRelIndex] =
+ ExecInitExtraTupleSlot(mtstate->ps.state, mtstate->mt_plan_tupdesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists,
+ thisRelIndex);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ thisRelIndex,
+ eflags);
+ }
+
+ /*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ if (node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists,
+ thisRelIndex);
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* RETURNING list */
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists,
+ thisRelIndex);
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (node->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (node->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(mtstate->mt_nrels == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a slot
+ * of the table's type here, because the slot will be used to insert
+ * into the table, and for RETURNING processing - which may access
+ * system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(node->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (node->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) node->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Prepare to generate tuples suitable for the target relation.
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ if (operation == CMD_INSERT)
+ {
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (!tle->resjunk)
+ resultTargetList = lappend(resultTargetList, tle);
+ else
+ need_projection = true;
+ }
+ }
+ else
+ {
+ resultTargetList = (List *) list_nth(node->updateTargetLists,
+ thisRelIndex);
+ need_projection = true;
+ }
+
+ /*
+ * The clean list must produce a tuple suitable for the result
+ * relation.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, resultTargetList);
+ }
+
+ if (need_projection)
+ {
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ /*
+ * For UPDATE, we use the old tuple to fill up missing values in
+ * the tuple produced by the plan to get the new tuple.
+ */
+ if (operation == CMD_UPDATE)
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildProjectionInfo(resultTargetList,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps,
+ relDesc);
+ }
+
+ /*
+ * For UPDATE/DELETE, find the appropriate junk attr now.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+ /* HACK: we require it to be present for updates. */
+ if (mtstate->operation == CMD_UPDATE &&
+ !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ else
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ /*
+ * For UPDATE on a partitioned tables, tuple routing might be needed if
+ * if the plan says so or a BEFORE UPDATE trigger is present on the
+ * partition which might modify the partition-key values.
+ */
+ if (mtstate->rootResultRelInfo &&
+ operation == CMD_UPDATE &&
+ (node->partColsUpdated ||
+ (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_update_before_row)))
+ update_tuple_routing_needed = true;
+
+ /* Tuple routing state may already have been initialized. */
+ if (update_tuple_routing_needed &&
+ mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rootRel);
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
+ }
+
+ /*
+ * 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.
+ */
+ if (update_tuple_routing_needed ||
+ (mtstate->mt_transition_capture &&
+ mtstate->operation != CMD_INSERT &&
+ thisRelIndex > 0))
+ {
+ Relation targetRel = getTargetResultRelInfo(mtstate)->ri_RelationDesc;
+
+ resultRelInfo->ri_childToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return resultRelInfo;
+}
+
+/*
* ExecGetInsertNewTuple
* This prepares a "new" tuple ready to be put into a result relation
* by removing any junk columns of the tuple produced by a plan.
@@ -1217,7 +1624,6 @@ ExecUpdate(ModifyTableState *mtstate,
TM_Result result;
TM_FailureData tmfd;
List *recheckIndexes = NIL;
- TupleConversionMap *saved_tcs_map = NULL;
/*
* abort the operation if not running transactions
@@ -1342,7 +1748,6 @@ lreplace:;
TupleTableSlot *ret_slot;
TupleTableSlot *epqslot = NULL;
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
- int map_index;
TupleConversionMap *tupconv_map;
/*
@@ -1413,16 +1818,6 @@ lreplace:;
}
/*
- * Updates set the transition capture map only when a new subplan
- * is chosen. But for inserts, it is set for each row. So after
- * INSERT, we need to revert back to the map created for UPDATE;
- * otherwise the next UPDATE will incorrectly use the one created
- * for INSERT. So first save the one created for UPDATE.
- */
- if (mtstate->mt_transition_capture)
- saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
-
- /*
* resultRelInfo is one of the per-subplan resultRelInfos. So we
* should convert the tuple into root's tuple descriptor, since
* ExecInsert() starts the search from root. The tuple conversion
@@ -1430,9 +1825,7 @@ lreplace:;
* retrieve the one for this resultRel, we need to know the
* position of the resultRel in mtstate->resultRelInfo[].
*/
- map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nrels);
- tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
+ tupconv_map = resultRelInfo->ri_childToRootMap;
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1451,11 +1844,13 @@ lreplace:;
/* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
+
+ /*
+ * Reset the transition state that may possibly have been written
+ * by INSERT.
+ */
if (mtstate->mt_transition_capture)
- {
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map = saved_tcs_map;
- }
return ret_slot;
}
@@ -1921,7 +2316,7 @@ static ResultRelInfo *
getTargetResultRelInfo(ModifyTableState *node)
{
/*
- * Note that if the node modifies a partitioned table, node->resultRelInfo
+ * Note that if the node modifies a partitioned table, plan->resultRelInfo
* points to the first leaf partition, not the root table.
*/
if (node->rootResultRelInfo != NULL)
@@ -1984,28 +2379,6 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
RelationGetRelid(targetRelInfo->ri_RelationDesc),
CMD_UPDATE);
-
- /*
- * If we found that we need to collect transition tuples then we may also
- * need tuple conversion maps for any children that have TupleDescs that
- * aren't compatible with the tuplestores. (We can share these maps
- * between the regular and ON CONFLICT cases.)
- */
- if (mtstate->mt_transition_capture != NULL ||
- mtstate->mt_oc_transition_capture != NULL)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
-
- /*
- * Install the conversion map for the first plan for UPDATE and DELETE
- * operations. It will be advanced each time we switch to the next
- * plan. (INSERT operations set it every time, so we need not update
- * mtstate->mt_oc_transition_capture here.)
- */
- if (mtstate->mt_transition_capture && mtstate->operation != CMD_INSERT)
- mtstate->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(mtstate, 0);
- }
}
/*
@@ -2047,37 +2420,16 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
estate->es_result_relation_info = partrel;
/*
- * If we're capturing transition tuples, we might need to convert from the
- * partition rowtype to root partitioned table's rowtype.
+ * If we're capturing transition tuples and there are no BR triggers to
+ * modify the row, we can simply put the original tuple into the
+ * transition tuplestore.
*/
- if (mtstate->mt_transition_capture != NULL)
- {
- if (partrel->ri_TrigDesc &&
- partrel->ri_TrigDesc->trig_insert_before_row)
- {
- /*
- * If there are any BEFORE triggers on the partition, we'll have
- * to be ready to convert their result back to tuplestore format.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted tuple, to
- * avoid a needless round trip conversion.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
- mtstate->mt_transition_capture->tcs_map = NULL;
- }
- }
- if (mtstate->mt_oc_transition_capture != NULL)
- {
- mtstate->mt_oc_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
+ if (mtstate->mt_transition_capture != NULL &&
+ !(partrel->ri_TrigDesc &&
+ partrel->ri_TrigDesc->trig_insert_before_row))
+ mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
+ else if (mtstate->mt_transition_capture != NULL)
+ mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
/*
* Convert the tuple, if necessary.
@@ -2093,58 +2445,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
return slot;
}
-/*
- * Initialize the child-to-root tuple conversion map array for UPDATE subplans.
- *
- * This map array is required to convert the tuple from the subplan result rel
- * to the target table descriptor. This requirement arises for two independent
- * scenarios:
- * 1. For update-tuple-routing.
- * 2. For capturing tuples in transition tables.
- */
-static void
-ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
-{
- ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
- ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
- TupleDesc outdesc;
- int numResultRelInfos = mtstate->mt_nrels;
- int i;
-
- /*
- * Build array of conversion maps from each child's TupleDesc to the one
- * used in the target relation. The map pointers may be NULL when no
- * conversion is necessary, which is hopefully a common case.
- */
-
- /* Get tuple descriptor of the target rel. */
- outdesc = RelationGetDescr(targetRelInfo->ri_RelationDesc);
-
- mtstate->mt_per_subplan_tupconv_maps = (TupleConversionMap **)
- palloc(sizeof(TupleConversionMap *) * numResultRelInfos);
-
- for (i = 0; i < numResultRelInfos; ++i)
- {
- mtstate->mt_per_subplan_tupconv_maps[i] =
- convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
- outdesc);
- }
-}
-
-/*
- * For a given subplan index, get the tuple conversion map.
- */
-static TupleConversionMap *
-tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
-{
- /* If nobody else set the per-subplan array of maps, do so ourselves. */
- if (mtstate->mt_per_subplan_tupconv_maps == NULL)
- ExecSetupChildParentMapForSubplan(mtstate);
-
- Assert(whichplan >= 0 && whichplan < mtstate->mt_nrels);
- return mtstate->mt_per_subplan_tupconv_maps[whichplan];
-}
-
/* ----------------------------------------------------------------
* ExecModifyTable
*
@@ -2159,8 +2459,10 @@ ExecModifyTable(PlanState *pstate)
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
EState *estate = node->ps.state;
CmdType operation = node->operation;
+ int firstRelIndex = ((ModifyTable *) node->ps.plan)->resultRelIndex;
ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo;
+ int whichrel;
PlanState *subplanstate;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
@@ -2202,7 +2504,8 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichrel;
+ whichrel = 0; /* default result rel */
+ resultRelInfo = ExecGetResultRelInfo(node, firstRelIndex + whichrel, true);
subplanstate = node->mt_plans[node->mt_whichplan];
/*
@@ -2262,31 +2565,18 @@ ExecModifyTable(PlanState *pstate)
/* Table OID -> ResultRelInfo. */
resultRelInfo = ExecLookupModifyResultRelByOid(node, tableoid,
- &node->mt_whichrel);
- Assert(node->mt_whichrel >= 0 &&
- node->mt_whichrel < node->mt_nrels);
+ &whichrel);
+ Assert(whichrel >= 0 && whichrel < node->mt_nrels);
estate->es_result_relation_info = resultRelInfo;
-
- /* Prepare to convert transition tuples from this child. */
- if (node->mt_transition_capture != NULL)
- {
- node->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichrel);
- }
- if (node->mt_oc_transition_capture != NULL)
- {
- node->mt_oc_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichrel);
- }
}
/*
* Ensure input tuple is the right format for the target relation.
*/
- if (node->mt_scans[node->mt_whichrel]->tts_ops != planSlot->tts_ops)
+ if (node->mt_scans[whichrel]->tts_ops != planSlot->tts_ops)
{
- ExecCopySlot(node->mt_scans[node->mt_whichrel], planSlot);
- planSlot = node->mt_scans[node->mt_whichrel];
+ ExecCopySlot(node->mt_scans[whichrel], planSlot);
+ planSlot = node->mt_scans[whichrel];
}
/*
@@ -2450,14 +2740,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
CmdType operation = node->operation;
int nplans = list_length(node->plans);
int nrels = list_length(node->resultRelations);
- ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- TupleDesc plan_result_type;
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)));
@@ -2475,14 +2760,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_done = false;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
- mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nrels);
- /* If modifying a partitioned table, initialize the root table info */
- if (node->rootResultRelIndex >= 0)
- mtstate->rootResultRelInfo = estate->es_root_result_relations +
- node->rootResultRelIndex;
-
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2491,7 +2770,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
mtstate->mt_nrels = nrels;
- mtstate->mt_whichrel = 0;
/*
* Call ExecInitNode on the only plan to be executed and save the result
@@ -2502,12 +2780,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* (see contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify()
* as one example).
*/
- saved_resultRelInfo = estate->es_result_relation_info;
- estate->es_result_relation_info = mtstate->resultRelInfo;
subplan = linitial(node->plans);
mtstate->mt_plans[0] = ExecInitNode(subplan, estate, eflags);
- estate->es_result_relation_info = saved_resultRelInfo;
- plan_result_type = ExecGetResultType(mtstate->mt_plans[0]);
+ mtstate->mt_plan_tupdesc = ExecGetResultType(mtstate->mt_plans[0]);
if (mtstate->mt_nrels > 1)
{
@@ -2518,6 +2793,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_tableOidAttno =
ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
Assert(AttributeNumberIsValid(mtstate->mt_tableOidAttno));
+
+ /*
+ * Also, initialize a hash table to look up UPDATE/DELETE result
+ * relations.
+ */
+ ExecHashSubPlanResultRelsByOid(mtstate);
}
/* Initialize some global state for RETURNING projections. */
@@ -2549,226 +2830,25 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
/*
- * Per result relation initializations.
- * TODO: do this lazily.
+ * If modifying a partitioned table, initialize the root table info.
*/
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nrels; i++)
- {
- List *resultTargetList = NIL;
- bool need_projection = false;
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, plan_result_type,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Now init the plan for this result rel */
- estate->es_result_relation_info = resultRelInfo;
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- i,
- eflags);
- }
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- if (node->withCheckOptionLists)
- {
- List *wcoList = (List *) list_nth(node->withCheckOptionLists, i);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- }
- if (node->returningLists)
- {
- List *rlist = (List *) list_nth(node->returningLists, i);
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- slot = mtstate->ps.ps_ResultTupleSlot;
- Assert(slot != NULL);
- econtext = mtstate->ps.ps_ExprContext;
- Assert(econtext != NULL);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- }
-
- /*
- * Prepare to generate tuples suitable for the target relation.
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- if (operation == CMD_INSERT)
- {
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (!tle->resjunk)
- resultTargetList = lappend(resultTargetList, tle);
- else
- need_projection = true;
- }
- }
- else
- {
- resultTargetList = (List *) list_nth(node->updateTargetLists,
- i);
- need_projection = true;
- }
-
- /*
- * The clean list must produce a tuple suitable for the result
- * relation.
- */
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- resultTargetList);
- }
-
- if (need_projection)
- {
- TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
-
- /*
- * For UPDATE, we use the old tuple to fill up missing values in
- * the tuple produced by the plan to get the new tuple.
- */
- if (operation == CMD_UPDATE)
- resultRelInfo->ri_oldTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
- resultRelInfo->ri_newTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* need an expression context to do the projection */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- resultRelInfo->ri_projectNew =
- ExecBuildProjectionInfo(resultTargetList,
- mtstate->ps.ps_ExprContext,
- resultRelInfo->ri_newTupleSlot,
- &mtstate->ps,
- relDesc);
- }
-
- /*
- * For UPDATE/DELETE, find the appropriate junk attr now.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
- if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist,
- "wholerow");
- /* HACK: we require it to be present for updates. */
- if (mtstate->operation == CMD_UPDATE &&
- !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk wholerow column");
- }
- else
- {
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
- if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo++;
- }
+ if (node->rootResultRelIndex >= 0)
+ mtstate->rootResultRelInfo =
+ ExecGetRootResultRelInfo(mtstate, node->rootResultRelIndex);
+ if (mtstate->rootResultRelInfo == NULL)
+ mtstate->resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex, true);
/* Get the target relation */
rel = (getTargetResultRelInfo(mtstate))->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 an INSERT. If an UPDATE might
+ * need it, ExecBuildResultRelInfo will build it when initializing
+ * a partition's ResultRelInfo.
*/
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);
@@ -2780,83 +2860,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * Construct mapping from each of the per-subplan partition attnos to the
- * root attno. This is required when during update row movement the tuple
- * descriptor of a source partition does not match the root partitioned
- * table descriptor. In such a case we need to convert tuples to the root
- * tuple descriptor, because the search for destination partition starts
- * from the root. We'll also need a slot to store these converted tuples.
- * We can skip this setup if it's not a partition key update.
- */
- if (update_tuple_routing_needed)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
- mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- 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);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
- /*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
* EvalPlanQual mechanism needs to be told about them. Locate the
@@ -2884,13 +2887,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize a hash table to look up UPDATE/DELETE result relations by
- * OID if there is more than one.
- */
- if (mtstate->operation != CMD_INSERT && mtstate->mt_nrels > 1)
- ExecHashSubPlanResultRelsByOid(mtstate);
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2920,20 +2916,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nrels; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index a752a12..42c4fc3 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -213,7 +213,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
resultRelInfo = makeNode(ResultRelInfo);
InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index a40ddf5..d1f436c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -45,10 +45,6 @@ typedef struct TriggerData
/*
* The state for capturing old and new tuples into transition tables for a
* single ModifyTable node (or other operation source, e.g. copy.c).
- *
- * This is per-caller to avoid conflicts in setting tcs_map or
- * tcs_original_insert_tuple. Note, however, that the pointed-to
- * private data may be shared across multiple callers.
*/
struct AfterTriggersTableData; /* private in trigger.c */
@@ -66,14 +62,6 @@ typedef struct TransitionCaptureState
bool tcs_insert_new_table;
/*
- * For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the
- * new and old tuples from a child table's format to the format of the
- * relation named in a query so that it is compatible with the transition
- * tuplestores. The caller must store the conversion map here if so.
- */
- TupleConversionMap *tcs_map;
-
- /*
* For INSERT and COPY, it would be wasteful to convert tuples from child
* format to parent format after they have already been converted in the
* opposite direction during routing. In that case we bypass conversion
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1f4efd6..317e739 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -606,6 +606,10 @@ extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
/* prototypes from functions in nodeModifyTable.c */
extern ResultRelInfo *ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
Oid reloid, int *whichrel);
+extern ResultRelInfo *ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it);
+extern ResultRelInfo *ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex);
+
/* needed by trigger.c */
extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 4ec4ebd..a30f5cf 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -20,5 +20,4 @@ extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, Cmd
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
-
#endif /* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 88ac0b2..a1d2b2b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -494,6 +494,12 @@ typedef struct ResultRelInfo
TupleTableSlot *ri_oldTupleSlot;
TupleTableSlot *ri_newTupleSlot;
ProjectionInfo *ri_projectNew;
+
+ /*
+ * Map to convert child sublan tuples to root parent format, set iff
+ * either update row movement or transition tuple capture is active.
+ */
+ TupleConversionMap *ri_childToRootMap;
} ResultRelInfo;
/* ----------------
@@ -525,7 +531,7 @@ typedef struct EState
CommandId es_output_cid;
/* Info about target table(s) for insert/update/delete queries: */
- ResultRelInfo *es_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_result_relations; /* array of ResultRelInfo pointers */
int es_num_result_relations; /* length of array */
ResultRelInfo *es_result_relation_info; /* currently active array elt */
@@ -535,7 +541,8 @@ typedef struct EState
* es_result_relations, but we need access to the roots for firing
* triggers and for runtime tuple routing.
*/
- ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_root_result_relations; /* array of ResultRelInfo
+ * pointers */
int es_num_root_result_relations; /* length of the array */
PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */
@@ -1173,7 +1180,7 @@ typedef struct ModifyTableState
int mt_nplans; /* number of plans in mt_plans (only 1!) */
int mt_whichplan; /* which one is being executed (always 0th!) */
int mt_nrels; /* number of result rels in the arrays */
- int mt_whichrel; /* Array index of target rel being targeted */
+ TupleDesc mt_plan_tupdesc; /* TupleDesc of plan's output */
TupleTableSlot **mt_scans; /* input tuple for each result relation */
/*
@@ -1182,7 +1189,7 @@ typedef struct ModifyTableState
*/
int mt_tableOidAttno;
- ResultRelInfo *resultRelInfo; /* Target relations */
+ ResultRelInfo *resultRelInfo; /* Target relation */
HTAB *mt_subplan_resultrel_hash; /* hash table to look up result
* relation by OID. */
ResultRelInfo *rootResultRelInfo; /* root target relation (partitioned
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index a295c08..ea3200a 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -1837,6 +1837,53 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
Filter: ((b >= $1) AND (b <= $2) AND (a < $0))
(10 rows)
+-- Runtime pruning for UPDATE/DELETE (mainly notice result relations listed)
+prepare upd_q (int, int) as
+update ab set a = a where a = $1 and b = $2;
+-- PARAM_EXTERN
+explain (analyze, costs off, summary off, timing off) execute upd_q (1, 1);
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on ab (actual rows=0 loops=1)
+ Update on ab_a1_b1 ab_1
+ -> Append (actual rows=0 loops=1)
+ Subplans Removed: 8
+ -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Filter: ((a = $1) AND (b = $2))
+(6 rows)
+
+-- PARAM_EXEC
+explain (analyze, costs off, summary off, timing off)
+update ab set a = a where a = (select 1) and b = (select 1);
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on ab (actual rows=0 loops=1)
+ Update on ab_a1_b1 ab_1
+ InitPlan 1 (returns $0)
+ -> Result (actual rows=1 loops=1)
+ InitPlan 2 (returns $1)
+ -> Result (actual rows=1 loops=1)
+ -> Append (actual rows=0 loops=1)
+ -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a1_b2 ab_2 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a1_b3 ab_3 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b1 ab_4 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b2 ab_5 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a2_b3 ab_6 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b1 ab_7 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b2 ab_8 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+ -> Seq Scan on ab_a3_b3 ab_9 (never executed)
+ Filter: ((a = $0) AND (b = $1))
+(25 rows)
+
-- Test a backwards Append scan
create table list_part (a int) partition by list (a);
create table list_part1 partition of list_part for values in (1);
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 6658455..e7b617d 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -389,6 +389,18 @@ select a from ab where b between $1 and $2 and a < (select 3);
explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
+-- Runtime pruning for UPDATE/DELETE (mainly notice result relations listed)
+
+prepare upd_q (int, int) as
+update ab set a = a where a = $1 and b = $2;
+
+-- PARAM_EXTERN
+explain (analyze, costs off, summary off, timing off) execute upd_q (1, 1);
+
+-- PARAM_EXEC
+explain (analyze, costs off, summary off, timing off)
+update ab set a = a where a = (select 1) and b = (select 1);
+
-- Test a backwards Append scan
create table list_part (a int) partition by list (a);
create table list_part1 partition of list_part for values in (1);
--
1.8.3.1
v2-0003-Revise-how-some-FDW-executor-APIs-obtain-ResultRe.patchapplication/octet-stream; name=v2-0003-Revise-how-some-FDW-executor-APIs-obtain-ResultRe.patchDownload
From 63529c89756c329a40101f425f39d8d64394b953 Mon Sep 17 00:00:00 2001
From: Etsuro Fujita <efujita@postgresql.org>
Date: Thu, 8 Aug 2019 21:41:12 +0900
Subject: [PATCH v2 3/8] Revise how some FDW executor APIs obtain ResultRelInfo
This adds a field to ForeignScan node to get the index of the target
foreign table in the global list of query result relations (the
es_result_relations array).
So, BeginDirectModify() uses that index to get the RT index of the
target relation from the global list of target relation RT indexes.
Also, IterateDirectModify() uses that index to get the ResultRelInfo
from es_result_relations.
Amit Langote, Etsuro Fujita
Discussion: https://postgr.es/m/20190718010911.l6xcdv6birtxiei4@alap3.anarazel.de
---
contrib/postgres_fdw/postgres_fdw.c | 34 +++++++++++++++++++++++++++------
doc/src/sgml/fdwhandler.sgml | 5 +++--
src/backend/executor/nodeForeignscan.c | 11 +++++++----
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 2 ++
src/backend/optimizer/plan/setrefs.c | 15 +++++++++++++++
src/include/nodes/plannodes.h | 3 +++
9 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 697ec68..4a495bf 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -218,6 +218,7 @@ typedef struct PgFdwDirectModifyState
int num_tuples; /* # of result tuples */
int next_tuple; /* index of next one to return */
Relation resultRel; /* relcache entry for the target relation */
+ ResultRelInfo *resultRelInfo; /* ResultRelInfo for the target relation */
AttrNumber *attnoMap; /* array of attnums of input user columns */
AttrNumber ctidAttno; /* attnum of input ctid column */
AttrNumber oidAttno; /* attnum of input oid column */
@@ -361,7 +362,8 @@ static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
-static void postgresBeginDirectModify(ForeignScanState *node, int eflags);
+static void postgresBeginDirectModify(ForeignScanState *node,
+ int eflags);
static TupleTableSlot *postgresIterateDirectModify(ForeignScanState *node);
static void postgresEndDirectModify(ForeignScanState *node);
static void postgresExplainForeignScan(ForeignScanState *node,
@@ -2321,6 +2323,11 @@ postgresPlanDirectModify(PlannerInfo *root,
rebuild_fdw_scan_tlist(fscan, returningList);
}
+ /*
+ * Set the index of the subplan result rel.
+ */
+ fscan->resultRelIndex = subplan_index;
+
table_close(rel, NoLock);
return true;
}
@@ -2330,10 +2337,12 @@ postgresPlanDirectModify(PlannerInfo *root,
* Prepare a direct foreign table modification
*/
static void
-postgresBeginDirectModify(ForeignScanState *node, int eflags)
+postgresBeginDirectModify(ForeignScanState *node,
+ int eflags)
{
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
PgFdwDirectModifyState *dmstate;
Index rtindex;
RangeTblEntry *rte;
@@ -2358,7 +2367,8 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does.
*/
- rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
+ Assert(fsplan->resultRelIndex >= 0);
+ rtindex = list_nth_int(resultRelations, fsplan->resultRelIndex);
rte = exec_rt_fetch(rtindex, estate);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
@@ -2391,6 +2401,9 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
dmstate->rel = NULL;
}
+ /* ResultRelInfo is set when first needed by IterateDirectModify(). */
+ dmstate->resultRelInfo = NULL;
+
/* Initialize state variable */
dmstate->num_tuples = -1; /* -1 means not set yet */
@@ -2453,7 +2466,16 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = dmstate->resultRelInfo;
+
+ if (resultRelInfo == NULL)
+ {
+ ForeignScan *fscan = (ForeignScan *) node->ss.ps.plan;
+
+ resultRelInfo = &estate->es_result_relations[fscan->resultRelIndex];
+ Assert(resultRelInfo != NULL);
+ dmstate->resultRelInfo = resultRelInfo;
+ }
/*
* If this is the first call after Begin, execute the statement.
@@ -4089,7 +4111,7 @@ get_returning_data(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = dmstate->resultRelInfo;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
TupleTableSlot *resultSlot;
@@ -4236,7 +4258,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
TupleTableSlot *slot,
EState *estate)
{
- ResultRelInfo *relInfo = estate->es_result_relation_info;
+ ResultRelInfo *relInfo = dmstate->resultRelInfo;
TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
TupleTableSlot *resultSlot;
Datum *values;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 6587678..7439cd1 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -917,8 +917,9 @@ IterateDirectModify(ForeignScanState *node);
tuple table slot (the node's <structfield>ScanTupleSlot</structfield> should be
used for this purpose). The data that was actually inserted, updated
or deleted must be stored in the
- <literal>es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
- of the node's <structname>EState</structname>.
+ <literal>ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
+ of the target foreign table's <structname>ResultRelInfo</structname>
+ passed to <function>BeginDirectModify</function>.
Return NULL if no more rows are available.
Note that this is called in a short-lived memory context that will be
reset between invocations. Create a memory context in
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 513471a..ec5f166 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -221,12 +221,15 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan or the direct modification.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
+ else
+ {
+ Assert(node->resultRelIndex >= 0);
+ fdwroutine->BeginDirectModify(scanstate, eflags);
+ }
return scanstate;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c988c96..4d4b434 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -762,6 +762,7 @@ _copyForeignScan(const ForeignScan *from)
COPY_NODE_FIELD(fdw_recheck_quals);
COPY_BITMAPSET_FIELD(fs_relids);
COPY_SCALAR_FIELD(fsSystemCol);
+ COPY_SCALAR_FIELD(resultRelIndex);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d168ecc..0997e74 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -699,6 +699,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
WRITE_NODE_FIELD(fdw_recheck_quals);
WRITE_BITMAPSET_FIELD(fs_relids);
WRITE_BOOL_FIELD(fsSystemCol);
+ WRITE_INT_FIELD(resultRelIndex);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 92bb7ad..872cf10 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2018,6 +2018,7 @@ _readForeignScan(void)
READ_NODE_FIELD(fdw_recheck_quals);
READ_BITMAPSET_FIELD(fs_relids);
READ_BOOL_FIELD(fsSystemCol);
+ READ_INT_FIELD(resultRelIndex);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index da4a804..1078704 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -5557,6 +5557,8 @@ make_foreignscan(List *qptlist,
node->fs_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;
+ /* resultRelIndex will be set by PlanDirectModify/setrefs.c, if needed */
+ node->resultRelIndex = -1;
return node;
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6a52c6c..3eb0fc1 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -906,6 +906,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
rc->rti += rtoffset;
rc->prti += rtoffset;
}
+ /*
+ * Caution: Do not change the relative ordering of this loop
+ * and the statement below that adds the result relations to
+ * root->glob->resultRelations, because we need to use the
+ * current value of list_length(root->glob->resultRelations)
+ * in some plans.
+ */
foreach(l, splan->plans)
{
lfirst(l) = set_plan_refs(root,
@@ -1245,6 +1252,14 @@ set_foreignscan_references(PlannerInfo *root,
}
fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
+
+ /*
+ * Adjust resultRelIndex if it's valid (note that we are called before
+ * adding the RT indexes of ModifyTable result relations to the global
+ * list)
+ */
+ if (fscan->resultRelIndex >= 0)
+ fscan->resultRelIndex += list_length(root->glob->resultRelations);
}
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 909820b..ffe1830 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -621,6 +621,9 @@ typedef struct ForeignScan
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
Bitmapset *fs_relids; /* RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
+ int resultRelIndex; /* index of foreign table in the list of query
+ * result relations for INSERT/UPDATE/DELETE;
+ * -1 for SELECT */
} ForeignScan;
/* ----------------
--
1.8.3.1
v2-0004-Do-not-set-rootResultRelIndex-unnecessarily.patchapplication/octet-stream; name=v2-0004-Do-not-set-rootResultRelIndex-unnecessarily.patchDownload
From b8e2969b117112800ec9ac0121af33d1f1d35a67 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 13:12:21 +0900
Subject: [PATCH v2 4/8] Do not set rootResultRelIndex unnecessarily
It's set in ModifyTable for all INSERT, UPDATE, and DELETE, however
only needed for the latter two operations.
---
src/backend/optimizer/plan/setrefs.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 3eb0fc1..5001966 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -935,8 +935,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
* If the main target relation is a partitioned table, also
* add the partition root's RT index to rootResultRelations,
* and remember its index in that list in rootResultRelIndex.
+ * We do this only for UPDATE/DELETE though, because in only
+ * in their case do we need to process the root relation
+ * separately from other result relations.
*/
- if (splan->rootRelation)
+ if (splan->rootRelation && splan->operation != CMD_INSERT)
{
splan->rootResultRelIndex =
list_length(root->glob->rootResultRelations);
--
1.8.3.1
v2-0001-Overhaul-UPDATE-s-targetlist-processing.patchapplication/octet-stream; name=v2-0001-Overhaul-UPDATE-s-targetlist-processing.patchDownload
From e436f04287a167697cc11ef036234203e4a99870 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Tue, 9 Jun 2020 22:14:20 +0900
Subject: [PATCH v2 1/8] Overhaul UPDATE's targetlist processing
Instead of emitting the full tuple matching the target table's tuple
descriptor, make the plan emit only the attributes that are assigned
values in the SET clause, plus row-identity junk attributes as before.
However, a full tuple to pass to the table AM API as the new UPDATE
tuple must still be constructed. To do so, a separate targetlist is
provided which combining the partial tuple produced by the plan and
unchanged values taken from the old tuple, which is refetched after
running the plan. This targetlist is passed separately in the
ModifyTable node.
With this change, FDW APIs for planning and executing updates should
no longer assume that the plan's output tuple covers all of the target
relation's columns. Especially, it shouldn't assume that it can use
the target attribute's attribute number to find the TLE that
corresponds to that attribute in the plan's targetlist.
TODO:
* Remove the hack to use a wholerow Var to construct the full tuple
to pass to postgres_fdw, because it hasn't been fixed considering
the above node.
---
contrib/postgres_fdw/expected/postgres_fdw.out | 94 +++----
contrib/postgres_fdw/postgres_fdw.c | 7 +-
src/backend/commands/trigger.c | 5 +-
src/backend/executor/execMain.c | 1 -
src/backend/executor/nodeModifyTable.c | 358 +++++++++++++++----------
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 19 +-
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 9 +-
src/backend/optimizer/plan/planner.c | 66 +++++
src/backend/optimizer/plan/setrefs.c | 55 ++++
src/backend/optimizer/prep/preptlist.c | 105 +++++++-
src/backend/optimizer/util/pathnode.c | 4 +-
src/backend/optimizer/util/plancat.c | 57 ++++
src/backend/optimizer/util/tlist.c | 19 ++
src/backend/rewrite/rewriteHandler.c | 30 ++-
src/include/executor/executor.h | 9 +
src/include/nodes/execnodes.h | 9 +-
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 37 +++
src/include/nodes/plannodes.h | 5 +-
src/include/optimizer/optimizer.h | 1 +
src/include/optimizer/pathnode.h | 2 +-
src/include/optimizer/plancat.h | 2 +
src/include/optimizer/prep.h | 6 +-
src/test/regress/expected/inherit.out | 12 +-
src/test/regress/expected/updatable_views.out | 22 +-
src/test/regress/expected/update.out | 8 +-
28 files changed, 714 insertions(+), 231 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 82fc129..45efaf7 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -5457,13 +5457,13 @@ UPDATE ft2 AS target SET (c2, c7) = (
FROM ft2 AS src
WHERE target.c1 = src.c1
) WHERE c1 > 1100;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------
Update on public.ft2 target
Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c7 = $3 WHERE ctid = $1
-> Foreign Scan on public.ft2 target
- Output: target.c1, $1, NULL::integer, target.c3, target.c4, target.c5, target.c6, $2, target.c8, (SubPlan 1 (returns $1,$2)), target.ctid
- Remote SQL: SELECT "C 1", c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE
+ Output: $1, $2, (SubPlan 1 (returns $1,$2)), target.ctid, target.*
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE
SubPlan 1 (returns $1,$2)
-> Foreign Scan on public.ft2 src
Output: (src.c2 * 10), src.c7
@@ -5493,9 +5493,9 @@ UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
Output: c1, c2, c3, c4, c5, c6, c7, c8
Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-> Foreign Scan on public.ft2
- Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid
+ Output: 'bar'::text, ctid, ft2.*
Filter: (postgres_fdw_abs(ft2.c1) > 2000)
- Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
(7 rows)
UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
@@ -5524,11 +5524,11 @@ UPDATE ft2 SET c3 = 'baz'
Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-> Nested Loop
- Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
+ Output: 'baz'::text, ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
Join Filter: (ft2.c2 === ft4.c1)
-> Foreign Scan on public.ft2
- Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid
- Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
+ Output: ft2.ctid, ft2.*, ft2.c2
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
-> Foreign Scan
Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3
Relations: (public.ft4) INNER JOIN (public.ft5)
@@ -6218,7 +6218,7 @@ UPDATE rw_view SET b = b + 5;
Update on public.foreign_tbl
Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
- Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid
+ Output: (foreign_tbl.b + 5), foreign_tbl.ctid, foreign_tbl.*
Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
(5 rows)
@@ -6232,7 +6232,7 @@ UPDATE rw_view SET b = b + 15;
Update on public.foreign_tbl
Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
- Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid
+ Output: (foreign_tbl.b + 15), foreign_tbl.ctid, foreign_tbl.*
Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
(5 rows)
@@ -6306,7 +6306,7 @@ UPDATE rw_view SET b = b + 5;
Foreign Update on public.foreign_tbl parent_tbl_1
Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl parent_tbl_1
- Output: parent_tbl_1.a, (parent_tbl_1.b + 5), parent_tbl_1.ctid
+ Output: (parent_tbl_1.b + 5), parent_tbl_1.ctid, parent_tbl_1.*
Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)
@@ -6321,7 +6321,7 @@ UPDATE rw_view SET b = b + 15;
Foreign Update on public.foreign_tbl parent_tbl_1
Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl parent_tbl_1
- Output: parent_tbl_1.a, (parent_tbl_1.b + 15), parent_tbl_1.ctid
+ Output: (parent_tbl_1.b + 15), parent_tbl_1.ctid, parent_tbl_1.*
Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)
@@ -6638,7 +6638,7 @@ UPDATE rem1 set f1 = 10; -- all columns should be transmitted
Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1
-> Foreign Scan on public.rem1
- Output: 10, f2, ctid, rem1.*
+ Output: 10, ctid, rem1.*
Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)
@@ -6871,7 +6871,7 @@ UPDATE rem1 set f2 = ''; -- can't be pushed down
Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1
-> Foreign Scan on public.rem1
- Output: f1, ''::text, ctid, rem1.*
+ Output: ''::text, ctid, rem1.*
Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)
@@ -6895,7 +6895,7 @@ UPDATE rem1 set f2 = ''; -- can't be pushed down
Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2
-> Foreign Scan on public.rem1
- Output: f1, ''::text, ctid, rem1.*
+ Output: ''::text, ctid, rem1.*
Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)
@@ -7205,18 +7205,18 @@ select * from bar where f1 in (select f1 from foo) for share;
-- Check UPDATE with inherited target and an inherited source table
explain (verbose, costs off)
update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
- QUERY PLAN
--------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2 bar_1
Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-> Hash Join
- Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
+ Output: (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
Inner Unique: true
Hash Cond: (bar.f1 = foo.f1)
-> Seq Scan on public.bar
- Output: bar.f1, bar.f2, bar.ctid
+ Output: bar.f2, bar.ctid, bar.f1
-> Hash
Output: foo.ctid, foo.f1, foo.*, foo.tableoid
-> HashAggregate
@@ -7229,11 +7229,11 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
-> Hash Join
- Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, foo.ctid, foo.*, foo.tableoid
+ Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, foo.ctid, foo.*, foo.tableoid
Inner Unique: true
Hash Cond: (bar_1.f1 = foo.f1)
-> Foreign Scan on public.bar2 bar_1
- Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid
+ Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Hash
Output: foo.ctid, foo.f1, foo.*, foo.tableoid
@@ -7273,7 +7273,7 @@ where bar.f1 = ss.f1;
Foreign Update on public.bar2 bar_1
Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-> Hash Join
- Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Output: (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
Hash Cond: (foo.f1 = bar.f1)
-> Append
-> Seq Scan on public.foo
@@ -7287,17 +7287,17 @@ where bar.f1 = ss.f1;
Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3)
Remote SQL: SELECT f1 FROM public.loct1
-> Hash
- Output: bar.f1, bar.f2, bar.ctid
+ Output: bar.f2, bar.ctid, bar.f1
-> Seq Scan on public.bar
- Output: bar.f1, bar.f2, bar.ctid
+ Output: bar.f2, bar.ctid, bar.f1
-> Merge Join
- Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, (ROW(foo.f1))
+ Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, (ROW(foo.f1))
Merge Cond: (bar_1.f1 = foo.f1)
-> Sort
- Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid
+ Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
Sort Key: bar_1.f1
-> Foreign Scan on public.bar2 bar_1
- Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid
+ Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Sort
Output: (ROW(foo.f1)), foo.f1
@@ -7471,7 +7471,7 @@ update bar set f2 = f2 + 100 returning *;
Update on public.bar
Foreign Update on public.bar2 bar_1
-> Seq Scan on public.bar
- Output: bar.f1, (bar.f2 + 100), bar.ctid
+ Output: (bar.f2 + 100), bar.ctid
-> Foreign Update on public.bar2 bar_1
Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2
(8 rows)
@@ -7503,9 +7503,9 @@ update bar set f2 = f2 + 100;
Foreign Update on public.bar2 bar_1
Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ctid = $1 RETURNING f1, f2, f3
-> Seq Scan on public.bar
- Output: bar.f1, (bar.f2 + 100), bar.ctid
+ Output: (bar.f2 + 100), bar.ctid
-> Foreign Scan on public.bar2 bar_1
- Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, bar_1.*
+ Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
(9 rows)
@@ -7574,10 +7574,10 @@ update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a re
Update on public.parent
Foreign Update on public.remt1 parent_1
-> Nested Loop
- Output: parent.a, (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b
+ Output: (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b
Join Filter: (parent.a = remt2.a)
-> Seq Scan on public.parent
- Output: parent.a, parent.b, parent.ctid
+ Output: parent.b, parent.ctid, parent.a
-> Foreign Scan on public.remt2
Output: remt2.b, remt2.*, remt2.a
Remote SQL: SELECT a, b FROM public.loct2
@@ -7832,7 +7832,7 @@ update utrtest set a = 1 where a = 1 or a = 2 returning *;
-> Foreign Update on public.remp utrtest_1
Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b
-> Seq Scan on public.locp utrtest_2
- Output: 1, utrtest_2.b, utrtest_2.ctid
+ Output: 1, utrtest_2.ctid
Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
(9 rows)
@@ -7848,13 +7848,13 @@ insert into utrtest values (2, 'qux');
-- Check case where the foreign partition isn't a subplan target rel
explain (verbose, costs off)
update utrtest set a = 1 where a = 2 returning *;
- QUERY PLAN
-------------------------------------------------
+ QUERY PLAN
+-----------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
Update on public.locp utrtest_1
-> Seq Scan on public.locp utrtest_1
- Output: 1, utrtest_1.b, utrtest_1.ctid
+ Output: 1, utrtest_1.ctid
Filter: (utrtest_1.a = 2)
(6 rows)
@@ -7884,7 +7884,7 @@ update utrtest set a = 1 returning *;
-> Foreign Update on public.remp utrtest_1
Remote SQL: UPDATE public.loct SET a = 1 RETURNING a, b
-> Seq Scan on public.locp utrtest_2
- Output: 1, utrtest_2.b, utrtest_2.ctid
+ Output: 1, utrtest_2.ctid
(8 rows)
update utrtest set a = 1 returning *;
@@ -7908,20 +7908,20 @@ update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
Update on public.locp utrtest_2
-> Hash Join
- Output: 1, utrtest_1.b, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1
+ Output: 1, utrtest_1.ctid, utrtest_1.*, "*VALUES*".*, "*VALUES*".column1
Hash Cond: (utrtest_1.a = "*VALUES*".column1)
-> Foreign Scan on public.remp utrtest_1
- Output: utrtest_1.b, utrtest_1.ctid, utrtest_1.a
+ Output: utrtest_1.ctid, utrtest_1.*, utrtest_1.a
Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
-> Hash
Output: "*VALUES*".*, "*VALUES*".column1
-> Values Scan on "*VALUES*"
Output: "*VALUES*".*, "*VALUES*".column1
-> Hash Join
- Output: 1, utrtest_2.b, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1
+ Output: 1, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1
Hash Cond: (utrtest_2.a = "*VALUES*".column1)
-> Seq Scan on public.locp utrtest_2
- Output: utrtest_2.b, utrtest_2.ctid, utrtest_2.a
+ Output: utrtest_2.ctid, utrtest_2.a
-> Hash
Output: "*VALUES*".*, "*VALUES*".column1
-> Values Scan on "*VALUES*"
@@ -7957,7 +7957,7 @@ update utrtest set a = 3 returning *;
Update on public.locp utrtest_1
Foreign Update on public.remp utrtest_2
-> Seq Scan on public.locp utrtest_1
- Output: 3, utrtest_1.b, utrtest_1.ctid
+ Output: 3, utrtest_1.ctid
-> Foreign Update on public.remp utrtest_2
Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b
(8 rows)
@@ -7975,19 +7975,19 @@ update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *;
Foreign Update on public.remp utrtest_2
Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
-> Hash Join
- Output: 3, utrtest_1.b, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1
+ Output: 3, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1
Hash Cond: (utrtest_1.a = "*VALUES*".column1)
-> Seq Scan on public.locp utrtest_1
- Output: utrtest_1.b, utrtest_1.ctid, utrtest_1.a
+ Output: utrtest_1.ctid, utrtest_1.a
-> Hash
Output: "*VALUES*".*, "*VALUES*".column1
-> Values Scan on "*VALUES*"
Output: "*VALUES*".*, "*VALUES*".column1
-> Hash Join
- Output: 3, utrtest_2.b, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1
+ Output: 3, utrtest_2.ctid, utrtest_2.*, "*VALUES*".*, "*VALUES*".column1
Hash Cond: (utrtest_2.a = "*VALUES*".column1)
-> Foreign Scan on public.remp utrtest_2
- Output: utrtest_2.b, utrtest_2.ctid, utrtest_2.a
+ Output: utrtest_2.ctid, utrtest_2.*, utrtest_2.a
Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
-> Hash
Output: "*VALUES*".*, "*VALUES*".column1
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9fc53ca..16c5f19 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -2150,6 +2150,7 @@ postgresPlanDirectModify(PlannerInfo *root,
List *params_list = NIL;
List *returningList = NIL;
List *retrieved_attrs = NIL;
+ ResultRelPlanInfo *resultInfo;
/*
* Decide whether it is safe to modify a foreign table directly.
@@ -2166,6 +2167,8 @@ postgresPlanDirectModify(PlannerInfo *root,
* joins needed.
*/
subplan = (Plan *) list_nth(plan->plans, subplan_index);
+ resultInfo = linitial(root->result_rel_list);
+ Assert(resultInfo != NULL);
if (!IsA(subplan, ForeignScan))
return false;
fscan = (ForeignScan *) subplan;
@@ -2211,7 +2214,7 @@ postgresPlanDirectModify(PlannerInfo *root,
if (attno <= InvalidAttrNumber) /* shouldn't happen */
elog(ERROR, "system-column update is not supported");
- tle = get_tle_by_resno(subplan->targetlist, attno);
+ tle = get_tle_by_resno(resultInfo->subplanTargetList, attno);
if (!tle)
elog(ERROR, "attribute number %d not found in subplan targetlist",
@@ -2271,7 +2274,7 @@ postgresPlanDirectModify(PlannerInfo *root,
case CMD_UPDATE:
deparseDirectUpdateSql(&sql, root, resultRelation, rel,
foreignrel,
- ((Plan *) fscan)->targetlist,
+ resultInfo->subplanTargetList,
targetAttrs,
remote_exprs, ¶ms_list,
returningList, &retrieved_attrs);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..58a5111 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2686,7 +2686,10 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
{
TupleTableSlot *epqslot_clean;
- epqslot_clean = ExecFilterJunk(relinfo->ri_junkFilter, epqslot_candidate);
+ Assert(relinfo->ri_projectNew != NULL);
+ Assert(relinfo->ri_oldTupleSlot != NULL);
+ epqslot_clean = ExecGetUpdateNewTuple(relinfo, epqslot_candidate,
+ tupleid, NULL);
if (newslot != epqslot_clean)
ExecCopySlot(newslot, epqslot_clean);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..166486d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1318,7 +1318,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_usesFdwDirectModify = false;
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_GeneratedExprs = NULL;
- resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
resultRelInfo->ri_onConflictArbiterIndexes = NIL;
resultRelInfo->ri_onConflict = NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..0a99818 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -75,6 +75,8 @@ static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
int whichplan);
+static TupleTableSlot *ExecGetNewInsertTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1043,6 +1045,65 @@ ldelete:;
return NULL;
}
+/*
+ * ExecProjectNewInsertTuple
+ * This prepares a "new" tuple ready to be put into a result relation
+ * by removing any junk columns of the tuple produced by a plan.
+ */
+static TupleTableSlot *
+ExecGetNewInsertTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot)
+{
+ ProjectionInfo *newProj = relinfo->ri_projectNew;
+ ExprContext *econtext;
+
+ if (newProj == NULL)
+ return planSlot;
+
+ econtext = newProj->pi_exprContext;
+ econtext->ecxt_outertuple = planSlot;
+ return ExecProject(newProj);
+}
+
+
+/*
+ * ExecProjectNewUpdateTuple
+ * This prepares a "new" tuple ready to be put into a result relation
+ * by combining the "plan" tuple and the "old" tuple.
+ *
+ * The "plan" tuple contains values for only those columns that were specified
+ * in the update statement's SET clause and some junk columns which must be
+ * filtered out. Values for the rest of the columns are taken from the "old"
+ * tuple.
+ *
+ * The caller may pass either tupleid of the old tuple or its HeapTuple.
+ */
+TupleTableSlot *
+ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot,
+ ItemPointerData *tupleid,
+ HeapTupleData *oldtuple)
+{
+ ProjectionInfo *newProj = relinfo->ri_projectNew;
+ ExprContext *econtext;
+ TupleTableSlot *oldSlot = relinfo->ri_oldTupleSlot;
+ Relation relation = relinfo->ri_RelationDesc;
+
+ Assert(newProj != NULL);
+ Assert(oldSlot != NULL);
+ Assert(tupleid != NULL || oldtuple != NULL);
+ ExecClearTuple(oldSlot);
+ if (tupleid != NULL)
+ table_tuple_fetch_row_version(relation, tupleid, SnapshotAny,
+ oldSlot);
+ else
+ ExecForceStoreHeapTuple(oldtuple, oldSlot, false);
+ econtext = newProj->pi_exprContext;
+ econtext->ecxt_scantuple = oldSlot;
+ econtext->ecxt_outertuple = planSlot;
+ return ExecProject(newProj);
+}
+
/* ----------------------------------------------------------------
* ExecUpdate
*
@@ -1269,7 +1330,8 @@ lreplace:;
return NULL;
else
{
- slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+ slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
+ tupleid, oldtuple);
goto lreplace;
}
}
@@ -1423,7 +1485,9 @@ lreplace:;
/* Tuple not passing quals anymore, exiting... */
return NULL;
- slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+ slot = ExecGetUpdateNewTuple(resultRelInfo,
+ epqslot, tupleid,
+ oldtuple);
goto lreplace;
case TM_Deleted:
@@ -2022,7 +2086,6 @@ ExecModifyTable(PlanState *pstate)
ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo;
PlanState *subplanstate;
- JunkFilter *junkfilter;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -2065,7 +2128,6 @@ ExecModifyTable(PlanState *pstate)
/* Preload local variables */
resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* es_result_relation_info must point to the currently active result
@@ -2110,7 +2172,6 @@ ExecModifyTable(PlanState *pstate)
{
resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
estate->es_result_relation_info = resultRelInfo;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
@@ -2165,80 +2226,78 @@ ExecModifyTable(PlanState *pstate)
tupleid = NULL;
oldtuple = NULL;
- if (junkfilter != NULL)
+ /*
+ * extract the 'ctid' or 'wholerow' junk attribute.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
- /*
- * extract the 'ctid' or 'wholerow' junk attribute.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- char relkind;
- Datum datum;
- bool isNull;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)
- {
- datum = ExecGetJunkAttribute(slot,
- junkfilter->jf_junkAttNo,
- &isNull);
- /* shouldn't ever get a null result... */
- if (isNull)
- elog(ERROR, "ctid is NULL");
-
- tupleid = (ItemPointer) DatumGetPointer(datum);
- tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
- tupleid = &tuple_ctid;
- }
+ char relkind;
+ Datum datum;
+ bool isNull;
- /*
- * Use the wholerow attribute, when available, to reconstruct
- * the old relation tuple.
- *
- * Foreign table updates have a wholerow attribute when the
- * relation has a row-level trigger. Note that the wholerow
- * attribute does not carry system columns. Foreign table
- * triggers miss seeing those, except that we know enough here
- * to set t_tableOid. Quite separately from this, the FDW may
- * fetch its own junk attrs to identify the row.
- *
- * Other relevant relkinds, currently limited to views, always
- * have a wholerow attribute.
- */
- else if (AttributeNumberIsValid(junkfilter->jf_junkAttNo))
- {
- datum = ExecGetJunkAttribute(slot,
- junkfilter->jf_junkAttNo,
- &isNull);
- /* shouldn't ever get a null result... */
- if (isNull)
- elog(ERROR, "wholerow is NULL");
-
- oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
- oldtupdata.t_len =
- HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
- ItemPointerSetInvalid(&(oldtupdata.t_self));
- /* Historically, view triggers see invalid t_tableOid. */
- oldtupdata.t_tableOid =
- (relkind == RELKIND_VIEW) ? InvalidOid :
- RelationGetRelid(resultRelInfo->ri_RelationDesc);
-
- oldtuple = &oldtupdata;
- }
- else
- Assert(relkind == RELKIND_FOREIGN_TABLE);
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)
+ {
+ Assert(resultRelInfo->ri_junkAttno > 0);
+ datum = ExecGetJunkAttribute(slot,
+ resultRelInfo->ri_junkAttno,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "ctid is NULL");
+
+ tupleid = (ItemPointer) DatumGetPointer(datum);
+ tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
+ tupleid = &tuple_ctid;
}
/*
- * apply the junkfilter if needed.
+ * Use the wholerow attribute, when available, to reconstruct
+ * the old relation tuple. The old tuple serves one of two
+ * purposes or both: 1) it serves as the OLD tuple for any row
+ * triggers on foreign tables, 2) it provides values for any
+ * missing columns for the NEW tuple of the UPDATEs targeting
+ * foreign tables, because the plan itself does not produce all
+ * the columns of the target table; see the "oldtuple" being
+ * passed to ExecGetUpdateNewTuple() below.
+ *
+ * Note that the wholerow attribute does not carry system columns,
+ * so foreign table triggers miss seeing those, except that we
+ * know enough here to set t_tableOid. Quite separately from this,
+ * the FDW may fetch its own junk attrs to identify the row.
+ *
+ * Other relevant relkinds, currently limited to views, always
+ * have a wholerow attribute.
*/
- if (operation != CMD_DELETE)
- slot = ExecFilterJunk(junkfilter, slot);
+ else if (AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ {
+ datum = ExecGetJunkAttribute(slot,
+ resultRelInfo->ri_junkAttno,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "wholerow is NULL");
+
+ oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
+ oldtupdata.t_len =
+ HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
+ ItemPointerSetInvalid(&(oldtupdata.t_self));
+ /* Historically, view triggers see invalid t_tableOid. */
+ oldtupdata.t_tableOid =
+ (relkind == RELKIND_VIEW) ? InvalidOid :
+ RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ oldtuple = &oldtupdata;
+ }
+ else
+ Assert(relkind == RELKIND_FOREIGN_TABLE);
}
+ estate->es_result_relation_info = resultRelInfo;
+
switch (operation)
{
case CMD_INSERT:
+ slot = ExecGetNewInsertTuple(resultRelInfo, planSlot);
/* Prepare for tuple routing if needed. */
if (proute)
slot = ExecPrepareTupleRouting(node, estate, proute,
@@ -2250,6 +2309,10 @@ ExecModifyTable(PlanState *pstate)
estate->es_result_relation_info = resultRelInfo;
break;
case CMD_UPDATE:
+ Assert(resultRelInfo->ri_projectNew);
+ Assert(resultRelInfo->ri_oldTupleSlot);
+ slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot, tupleid,
+ oldtuple);
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
&node->mt_epqstate, estate, node->canSetTag);
break;
@@ -2646,90 +2709,111 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* This section of code is also a convenient place to verify that the
* output of an INSERT or UPDATE matches the target table(s).
*/
+ for (i = 0; i < nplans; i++)
{
- bool junk_filter_needed = false;
+ List *resultTargetList = NIL;
+ bool need_projection = false;
- switch (operation)
+ resultRelInfo = &mtstate->resultRelInfo[i];
+ subplan = mtstate->mt_plans[i]->plan;
+
+ /*
+ * Prepare to generate tuples suitable for the target relation.
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
{
- case CMD_INSERT:
+ if (operation == CMD_INSERT)
+ {
foreach(l, subplan->targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
+ if (!tle->resjunk)
+ resultTargetList = lappend(resultTargetList, tle);
+ else
+ need_projection = true;
}
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
+ }
+ else
{
- JunkFilter *j;
- TupleTableSlot *junkresslot;
+ resultTargetList = (List *) list_nth(node->updateTargetLists,
+ i);
+ need_projection = true;
+ }
- subplan = mtstate->mt_plans[i]->plan;
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
+ /*
+ * The clean list must produce a tuple suitable for the result
+ * relation.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ resultTargetList);
+ }
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
+ if (need_projection)
+ {
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
+ /*
+ * For UPDATE, we use the old tuple to fill up missing values in
+ * the tuple produced by the plan to get the new tuple.
+ */
+ if (operation == CMD_UPDATE)
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ resultRelInfo->ri_projectNew =
+ ExecBuildProjectionInfo(resultTargetList,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps,
+ relDesc);
+ }
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
+ /*
+ * For UPDATE/DELETE, find the appropriate junk attr now.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ char relkind;
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "wholerow");
+ /* HACK: we require it to be present for updates. */
+ if (mtstate->operation == CMD_UPDATE &&
+ !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ else
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
}
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
}
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d8cf87e..c988c96 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -212,6 +212,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(plans);
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
+ COPY_NODE_FIELD(updateTargetLists);
COPY_NODE_FIELD(fdwPrivLists);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f1775..cd8dfcf 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -413,6 +413,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(plans);
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
+ WRITE_NODE_FIELD(updateTargetLists);
WRITE_NODE_FIELD(fdwPrivLists);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks);
@@ -2120,6 +2121,7 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
WRITE_NODE_FIELD(subroots);
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
+ WRITE_NODE_FIELD(updateTargetLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(onconflict);
WRITE_INT_FIELD(epqParam);
@@ -2235,6 +2237,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_NODE_FIELD(full_join_clauses);
WRITE_NODE_FIELD(join_info_list);
WRITE_NODE_FIELD(append_rel_list);
+ WRITE_NODE_FIELD(result_rel_list);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list);
WRITE_NODE_FIELD(fkey_list);
@@ -2249,7 +2252,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
WRITE_UINT_FIELD(qual_security_level);
- WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind);
WRITE_BOOL_FIELD(hasJoinRTEs);
WRITE_BOOL_FIELD(hasLateralRTEs);
WRITE_BOOL_FIELD(hasHavingQual);
@@ -2546,6 +2548,18 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
}
static void
+_outResultRelPlanInfo(StringInfo str, const ResultRelPlanInfo *node)
+{
+ WRITE_NODE_TYPE("RESULTRELPLANINFO");
+
+ WRITE_UINT_FIELD(resultRelation);
+ WRITE_NODE_FIELD(subplanTargetList);
+ WRITE_NODE_FIELD(updateTargetList);
+ WRITE_NODE_FIELD(withCheckOptions);
+ WRITE_NODE_FIELD(returningList);
+}
+
+static void
_outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node)
{
WRITE_NODE_TYPE("PLACEHOLDERINFO");
@@ -4148,6 +4162,9 @@ outNode(StringInfo str, const void *obj)
case T_AppendRelInfo:
_outAppendRelInfo(str, obj);
break;
+ case T_ResultRelPlanInfo:
+ _outResultRelPlanInfo(str, obj);
+ break;
case T_PlaceHolderInfo:
_outPlaceHolderInfo(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab..92bb7ad 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1644,6 +1644,7 @@ _readModifyTable(void)
READ_NODE_FIELD(plans);
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
+ READ_NODE_FIELD(updateTargetLists);
READ_NODE_FIELD(fdwPrivLists);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index eb9543f..07fc199 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -297,6 +297,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
bool partColsUpdated,
List *resultRelations, List *subplans, List *subroots,
List *withCheckOptionLists, List *returningLists,
+ List *updateTargetLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
GatherMergePath *best_path);
@@ -2690,6 +2691,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
best_path->subroots,
best_path->withCheckOptionLists,
best_path->returningLists,
+ best_path->updateTargetLists,
best_path->rowMarks,
best_path->onconflict,
best_path->epqParam);
@@ -6798,6 +6800,7 @@ make_modifytable(PlannerInfo *root,
bool partColsUpdated,
List *resultRelations, List *subplans, List *subroots,
List *withCheckOptionLists, List *returningLists,
+ List *updateTargetLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
@@ -6807,12 +6810,13 @@ make_modifytable(PlannerInfo *root,
ListCell *lc2;
int i;
- Assert(list_length(resultRelations) == list_length(subplans));
- Assert(list_length(resultRelations) == list_length(subroots));
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
list_length(resultRelations) == list_length(returningLists));
+ Assert(operation != CMD_UPDATE ||
+ (updateTargetLists != NIL &&
+ list_length(resultRelations) == list_length(updateTargetLists)));
node->plan.lefttree = NULL;
node->plan.righttree = NULL;
@@ -6857,6 +6861,7 @@ make_modifytable(PlannerInfo *root,
}
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
+ node->updateTargetLists = updateTargetLists;
node->rowMarks = rowMarks;
node->epqParam = epqParam;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4131019..afad8c6 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -636,6 +636,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->wt_param_id = -1;
root->non_recursive_path = NULL;
root->partColsUpdated = false;
+ root->result_rel_list = NIL;
/*
* If there is a WITH list, process each WITH query and either convert it
@@ -1231,6 +1232,7 @@ inheritance_planner(PlannerInfo *root)
List *withCheckOptionLists = NIL;
List *returningLists = NIL;
List *rowMarks;
+ List *updateTargetLists = NIL;
RelOptInfo *final_rel;
ListCell *lc;
ListCell *lc2;
@@ -1484,6 +1486,8 @@ inheritance_planner(PlannerInfo *root)
RangeTblEntry *child_rte;
RelOptInfo *sub_final_rel;
Path *subpath;
+ ResultRelPlanInfo *resultInfo;
+ AttrNumber resno;
/*
* expand_inherited_rtentry() always processes a parent before any of
@@ -1581,6 +1585,9 @@ inheritance_planner(PlannerInfo *root)
subroot->append_rel_list = copyObject(root->append_rel_list);
subroot->rowMarks = copyObject(root->rowMarks);
+ /* Child result relation's only ResultRelPlanInfo goes here. */
+ subroot->result_rel_list = NIL;
+
/*
* If this isn't the first child Query, adjust Vars and jointree
* entries to reference the appropriate set of subquery RTEs.
@@ -1701,6 +1708,27 @@ inheritance_planner(PlannerInfo *root)
returningLists = lappend(returningLists,
subroot->parse->returningList);
+ Assert(list_length(subroot->result_rel_list) == 1);
+ resultInfo = linitial(subroot->result_rel_list);
+ if (parse->commandType == CMD_UPDATE)
+ updateTargetLists = lappend(updateTargetLists,
+ resultInfo->updateTargetList);
+ root->result_rel_list = lappend(root->result_rel_list, resultInfo);
+
+ /*
+ * While we are here, renumber the top-level targetlist so that
+ * resnos match those in the top-level plan's targetlist.
+ * XXX - really, this is to prevent apply_tlist_labeling() from
+ * crashing.
+ */
+ resno = 1;
+ foreach(lc, subroot->processed_tlist)
+ {
+ TargetEntry *tle = lfirst(lc);
+
+ tle->resno = resno++;
+ }
+
Assert(!parse->onConflict);
}
@@ -1726,7 +1754,10 @@ inheritance_planner(PlannerInfo *root)
Path *dummy_path;
/* tlist processing never got done, either */
+ Assert(root->result_rel_list == NIL);
root->processed_tlist = preprocess_targetlist(root);
+ /* that should also have a made a ResultRelPlanInfo for us. */
+ Assert(list_length(root->result_rel_list) == 1);
final_rel->reltarget = create_pathtarget(root, root->processed_tlist);
/* Make a dummy path, cf set_dummy_rel_pathlist() */
@@ -1742,6 +1773,14 @@ inheritance_planner(PlannerInfo *root)
withCheckOptionLists = list_make1(parse->withCheckOptions);
if (parse->returningList)
returningLists = list_make1(parse->returningList);
+ /* ExecInitModifyTable insists that updateTargetList is present. */
+ if (parse->commandType == CMD_UPDATE)
+ {
+ ResultRelPlanInfo *resultInfo;
+
+ resultInfo = linitial(root->result_rel_list);
+ updateTargetLists = list_make1(resultInfo->updateTargetList);
+ }
/* Disable tuple routing, too, just to be safe */
root->partColsUpdated = false;
}
@@ -1794,6 +1833,7 @@ inheritance_planner(PlannerInfo *root)
resultRelations,
subpaths,
subroots,
+ updateTargetLists,
withCheckOptionLists,
returningLists,
rowMarks,
@@ -2333,6 +2373,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
List *withCheckOptionLists;
List *returningLists;
List *rowMarks;
+ List *updateTargetLists = NIL;
+ AttrNumber resno;
/*
* If target is a partition root table, we need to mark the
@@ -2368,6 +2410,29 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
else
rowMarks = root->rowMarks;
+ if (root->result_rel_list)
+ {
+ ResultRelPlanInfo *resultInfo =
+ linitial(root->result_rel_list);
+
+ if (resultInfo->updateTargetList)
+ updateTargetLists = list_make1(resultInfo->updateTargetList);
+ }
+
+ /*
+ * While we are here, renumber the top-level targetlist so that
+ * resnos match those in the top-level plan's targetlist.
+ * XXX - really, this is to prevent apply_tlist_labeling() from
+ * crashing.
+ */
+ resno = 1;
+ foreach(lc, root->processed_tlist)
+ {
+ TargetEntry *tle = lfirst(lc);
+
+ tle->resno = resno++;
+ }
+
path = (Path *)
create_modifytable_path(root, final_rel,
parse->commandType,
@@ -2378,6 +2443,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
list_make1_int(parse->resultRelation),
list_make1(path),
list_make1(root),
+ updateTargetLists,
withCheckOptionLists,
returningLists,
rowMarks,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index baefe0e..2c31a7c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -148,6 +148,9 @@ static List *set_returning_clause_references(PlannerInfo *root,
Plan *topplan,
Index resultRelation,
int rtoffset);
+static void set_update_tlist_references(PlannerInfo *root,
+ ModifyTable *splan,
+ int rtoffset);
/*****************************************************************************
@@ -812,6 +815,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
Assert(splan->plan.targetlist == NIL);
Assert(splan->plan.qual == NIL);
+ if (splan->operation == CMD_UPDATE)
+ set_update_tlist_references(root, splan, rtoffset);
+
splan->withCheckOptionLists =
fix_scan_list(root, splan->withCheckOptionLists, rtoffset);
@@ -2694,6 +2700,55 @@ set_returning_clause_references(PlannerInfo *root,
return rlist;
}
+/*
+ * Update splan->updateTargetLists to refer to the subplan's output where
+ * applicable.
+ */
+static void
+set_update_tlist_references(PlannerInfo *root,
+ ModifyTable *splan,
+ int rtoffset)
+{
+ ListCell *lc1,
+ *lc2,
+ *lc3;
+
+ forthree(lc1, splan->resultRelations,
+ lc2, splan->updateTargetLists,
+ lc3, root->result_rel_list)
+ {
+ Index resultRel = lfirst_int(lc1);
+ List *updateTargetList = lfirst(lc2);
+ ResultRelPlanInfo *resultInfo = lfirst(lc3);
+ AttrNumber resno;
+ ListCell *lc;
+ indexed_tlist *itlist;
+
+ Assert(resultInfo);
+
+ /*
+ * Make resnos of subplan tlist TLEs match their ordinal position in
+ * the list. It's okay to scribble on them now.
+ */
+ resno = 1;
+ foreach(lc, resultInfo->subplanTargetList)
+ {
+ TargetEntry *tle = lfirst(lc);
+
+ tle->resno = resno++;
+ }
+
+ itlist = build_tlist_index(resultInfo->subplanTargetList);
+ lfirst(lc2) = fix_join_expr(root,
+ updateTargetList,
+ itlist,
+ NULL,
+ resultRel,
+ rtoffset);
+
+ }
+}
+
/*****************************************************************************
* QUERY DEPENDENCY MANAGEMENT
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index d56d8c6..89f7d89 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -45,6 +45,7 @@
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
#include "optimizer/prep.h"
#include "optimizer/tlist.h"
#include "parser/parse_coerce.h"
@@ -111,12 +112,21 @@ preprocess_targetlist(PlannerInfo *root)
* for heap_form_tuple to work, the targetlist must match the exact order
* of the attributes. We also need to fill in any missing attributes. -ay
* 10/94
+ *
+ * For UPDATE, we don't expand the plan's targetlist. Instead, another
+ * targetlist to be computed separately from the plan's targetlist is
+ * built and passed to the executor in the ModifyTable node; see
+ * make_update_targetlist().
*/
tlist = parse->targetList;
- if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ if (command_type == CMD_INSERT)
tlist = expand_targetlist(tlist, command_type,
result_relation, target_relation);
+ /* Make ResultRelPlanInfo for a UPDATE/DELETE result relation. */
+ if (target_relation && command_type != CMD_INSERT)
+ make_result_relation_info(root, result_relation, target_relation);
+
/*
* Add necessary junk columns for rowmarked rels. These values are needed
* for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
@@ -415,6 +425,99 @@ expand_targetlist(List *tlist, int command_type,
return new_tlist;
}
+/*
+ * make_update_targetlist
+ * Makes UPDATE targetlist such that "plan" TLEs are used for the
+ * attributes for which they are present. For the rest, Vars are
+ * added to fetch the existing values in the "old" tuple of the
+ * attributes that are not dropped, and NULL constants otherwise.
+ */
+List *
+make_update_targetlist(PlannerInfo *root,
+ Index rti, Relation relation,
+ List *plan_tlist)
+{
+ List *new_tlist = NIL;
+ ListCell *plan_item;
+ int attrno,
+ numattrs;
+
+ plan_item = list_head(plan_tlist);
+
+ numattrs = RelationGetNumberOfAttributes(relation);
+ for (attrno = 1; attrno <= numattrs; attrno++)
+ {
+ Form_pg_attribute att = TupleDescAttr(relation->rd_att, attrno - 1);
+ TargetEntry *new_tle = NULL;
+ Node *new_expr;
+
+ if (plan_item != NULL)
+ {
+ TargetEntry *plan_tle = (TargetEntry *) lfirst(plan_item);
+
+ if (!plan_tle->resjunk && plan_tle->resno == attrno)
+ {
+ /* Don't use the plan TLE as is; it gets changed later. */
+ new_tle = copyObject(plan_tle);
+ plan_item = lnext(plan_tlist, plan_item);
+ }
+ }
+
+
+ /*
+ * If not plan TLE present, generate a Var reference to the existing
+ * value of the attribute, so that it gets copied to the new tuple. But
+ * generate a NULL for dropped columns (we want to drop any old values).
+ */
+ if (new_tle == NULL)
+ {
+ if (!att->attisdropped)
+ {
+ new_expr = (Node *) makeVar(rti,
+ attrno,
+ att->atttypid,
+ att->atttypmod,
+ att->attcollation,
+ 0);
+ }
+ else
+ {
+ /* Insert NULL for dropped column */
+ new_expr = (Node *) makeConst(INT4OID,
+ -1,
+ InvalidOid,
+ sizeof(int32),
+ (Datum) 0,
+ true, /* isnull */
+ true /* byval */ );
+ }
+ new_tle = makeTargetEntry((Expr *) new_expr,
+ attrno,
+ pstrdup(NameStr(att->attname)),
+ false);
+ new_tlist = lappend(new_tlist, new_tle);
+ }
+ else
+ new_tlist = lappend(new_tlist, new_tle);
+ }
+
+
+ /*
+ * The remaining tlist entries should be resjunk. We don't need them
+ * for the update target list, but still check that they really are
+ * junk.
+ */
+ while (plan_item)
+ {
+ TargetEntry *plan_tle = (TargetEntry *) lfirst(plan_item);
+
+ if (!plan_tle->resjunk)
+ elog(ERROR, "targetlist is not sorted correctly");
+ plan_item = lnext(plan_tlist, plan_item);
+ }
+
+ return new_tlist;
+}
/*
* Locate PlanRowMark for given RT index, or return NULL if none
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e845a4b..64da7f5 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3490,6 +3490,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
* 'resultRelations' is an integer list of actual RT indexes of target rel(s)
* 'subpaths' is a list of Path(s) producing source data (one per rel)
* 'subroots' is a list of PlannerInfo structs (one per rel)
+ * 'updateTargetLists' is a list of UPDATE NEW tuple targetlists.
* 'withCheckOptionLists' is a list of WCO lists (one per rel)
* 'returningLists' is a list of RETURNING tlists (one per rel)
* 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3502,7 +3503,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
List *resultRelations, List *subpaths,
- List *subroots,
+ List *subroots, List *updateTargetLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
int epqParam)
@@ -3572,6 +3573,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->resultRelations = resultRelations;
pathnode->subpaths = subpaths;
pathnode->subroots = subroots;
+ pathnode->updateTargetLists = updateTargetLists;
pathnode->withCheckOptionLists = withCheckOptionLists;
pathnode->returningLists = returningLists;
pathnode->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..a9f833a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -35,6 +35,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/supportnodes.h"
+#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
@@ -2345,3 +2346,59 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
rel->partition_qual = partconstr;
}
}
+
+/*
+ * make_result_relation_info
+ * Adds information to the PlannerInfo about a UPDATE/DELETE result
+ * relation that will be made part of the ModifyTable node in the
+ * final plan.
+ *
+ * For UPDATE, this adds the targetlist (called 'updateTargetList') that is
+ * computed separately from the plan's top-level targetlist to get the tuples
+ * suitable for this result relation. Some entries are copied directly from
+ * the plan's top-level targetlist while others are simply Vars of the
+ * relation. In fact, each TLE copied from the top-level targetlist is later
+ * changed such that its expression is an OUTER Var referring to the
+ * appropriate column in the plan's output; see setrefs.c:
+ * set_update_tlist_references().
+ */
+void
+make_result_relation_info(PlannerInfo *root,
+ Index rti, Relation relation)
+{
+ Query *parse = root->parse;
+ ResultRelPlanInfo *resultInfo = makeNode(ResultRelPlanInfo);
+ List *subplanTargetList;
+ List *returningList = NIL;
+ List *updateTargetList = NIL;
+ List *withCheckOptions = NIL;
+
+ /* parse->targetList resnos get updated later, so make a copy. */
+ subplanTargetList = copyObject(parse->targetList);
+ withCheckOptions = parse->withCheckOptions;
+ returningList = parse->returningList;
+
+ if (parse->commandType == CMD_UPDATE)
+ {
+ List *planTargetList = subplanTargetList;
+
+ /* Fix to use plan TLEs over the existing ones. */
+ updateTargetList = make_update_targetlist(root, rti, relation,
+ planTargetList);
+ }
+
+ /*
+ * Of the following, everything except subplanTargetList goes into the
+ * ModifyaTable plan. subplanTargetListis is only kept around because
+ * set_update_tlist_references() must use result relation specific
+ * version of the original targetlist when matching the entries in
+ * their updateTargetLists.
+ */
+ resultInfo->resultRelation = rti;
+ resultInfo->subplanTargetList = subplanTargetList;
+ resultInfo->updateTargetList = updateTargetList;
+ resultInfo->withCheckOptions = withCheckOptions;
+ resultInfo->returningList = returningList;
+
+ root->result_rel_list = lappend(root->result_rel_list, resultInfo);
+}
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 02a3c6b..4c1677f 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -220,6 +220,25 @@ count_nonjunk_tlist_entries(List *tlist)
return len;
}
+/*
+ * filter_junk_tlist_entries
+ * What it says ...
+ */
+List *
+filter_junk_tlist_entries(List *tlist)
+{
+ List *result = NIL;
+ ListCell *l;
+
+ foreach(l, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (!tle->resjunk)
+ result = lappend(result, tle);
+ }
+ return result;
+}
/*
* tlist_same_exprs
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fe777c3..7bef075 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1480,17 +1480,27 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
target_relation);
/*
- * If we have a row-level trigger corresponding to the operation, emit
- * a whole-row Var so that executor will have the "old" row to pass to
- * the trigger. Alas, this misses system columns.
+ * We need the "old" tuple to fill up the values for unassigned-to
+ * attributes in the case of UPDATE. We will need it also if there
+ * are any DELETE row triggers.
+ *
+ * This is a HACK. The previous comment and if condition:
+ *
+ * -* If we have a row-level trigger corresponding to the operation, emit
+ * -* a whole-row Var so that executor will have the "old" row to pass to
+ * -* the trigger. Alas, this misses system columns.
+ * -if (target_relation->trigdesc &&
+ * - ((parsetree->commandType == CMD_UPDATE &&
+ * - (target_relation->trigdesc->trig_update_after_row ||
+ * - target_relation->trigdesc->trig_update_before_row)) ||
+ * - (parsetree->commandType == CMD_DELETE &&
+ * - (target_relation->trigdesc->trig_delete_after_row ||
+ * - target_relation->trigdesc->trig_delete_before_row))))
*/
- if (target_relation->trigdesc &&
- ((parsetree->commandType == CMD_UPDATE &&
- (target_relation->trigdesc->trig_update_after_row ||
- target_relation->trigdesc->trig_update_before_row)) ||
- (parsetree->commandType == CMD_DELETE &&
- (target_relation->trigdesc->trig_delete_after_row ||
- target_relation->trigdesc->trig_delete_before_row))))
+ if (parsetree->commandType == CMD_UPDATE ||
+ (target_relation->trigdesc &&
+ (target_relation->trigdesc->trig_delete_after_row ||
+ target_relation->trigdesc->trig_delete_before_row)))
{
var = makeWholeRowVar(target_rte,
parsetree->resultRelation,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c7deeac..903b110 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -603,4 +603,13 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from functions in nodeModifyTable.c */
+extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *mtstate, Oid reloid,
+ int *whichrel);
+/* needed by trigger.c */
+extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot,
+ ItemPointerData *tupleid,
+ HeapTupleData *oldtuple);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f5dfa32..520ef1b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -462,9 +462,6 @@ typedef struct ResultRelInfo
/* number of stored generated columns we need to compute */
int ri_NumGeneratedNeeded;
- /* for removing junk attributes from tuples */
- JunkFilter *ri_junkFilter;
-
/* list of RETURNING expressions */
List *ri_returningList;
@@ -491,6 +488,12 @@ typedef struct ResultRelInfo
/* For use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
+
+ /* Stuff for generating and holding "clean" tuples of this relation */
+ int ri_junkAttno;
+ TupleTableSlot *ri_oldTupleSlot;
+ TupleTableSlot *ri_newTupleSlot;
+ ProjectionInfo *ri_projectNew;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b..173f66d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -269,6 +269,7 @@ typedef enum NodeTag
T_PlaceHolderVar,
T_SpecialJoinInfo,
T_AppendRelInfo,
+ T_ResultRelPlanInfo,
T_PlaceHolderInfo,
T_MinMaxAggInfo,
T_PlannerParamItem,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 485d1b0..3cee3a3 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -218,6 +218,11 @@ struct PlannerInfo
*/
struct AppendRelInfo **append_rel_array;
+ /* Valid for UPDATE/DELETE. */
+ List *result_rel_list; /* List of ResultRelPlanInfo */
+ /* Same length as other "simple" rel arrays. */
+ struct ResultRelPlanInfo **result_rel_array;
+
/*
* all_baserels is a Relids set of all base relids (but not "other"
* relids) in the query; that is, the Relids identifier of the final join
@@ -1818,6 +1823,7 @@ typedef struct ModifyTablePath
List *resultRelations; /* integer list of RT indexes */
List *subpaths; /* Path(s) producing source data */
List *subroots; /* per-target-table PlannerInfos */
+ List *updateTargetLists; /* per-target-table tlists */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
@@ -2274,6 +2280,37 @@ typedef struct AppendRelInfo
} AppendRelInfo;
/*
+ * ResultRelPlanInfo
+ * Information about UPDATE/DELETE result relations
+ *
+ * For the original result relation, these fields point to the information
+ * in the original Query, except the target lists are different.
+ * subplanTargetList is set to a copy of PlannerInfo.processed_tlist after all
+ * the necessary junk columns have been added and is also what the top-level
+ * plan produces. updateTargetList is obtained by applying expand_targetlist()
+ * to subplanTargetList, followed by filtering out junk columns, so that its
+ * output is a tuple ready to be put into the result relation.
+ *
+ * For child result relations, relevant fields are obtained by translating Vars
+ * and for updateTargetList also adjusting the resnos to match the child
+ * attributes numbers.
+ *
+ * Everything execpt subplanTargetList goes into the ModifyTable node.
+ *
+ * See set_result_relation_info().
+ */
+typedef struct ResultRelPlanInfo
+{
+ NodeTag type;
+
+ Index resultRelation;
+ List *subplanTargetList;
+ List *updateTargetList;
+ List *withCheckOptions;
+ List *returningList;
+} ResultRelPlanInfo;
+
+/*
* For each distinct placeholder expression generated during planning, we
* store a PlaceHolderInfo node in the PlannerInfo node's placeholder_list.
* This stores info that is needed centrally rather than in each copy of the
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 83e0107..909820b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -226,9 +226,10 @@ typedef struct ModifyTable
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in plan's list */
int rootResultRelIndex; /* index of the partitioned table root */
- List *plans; /* plan(s) producing source data */
+ List *plans; /* Plan(s) producing source data */
List *withCheckOptionLists; /* per-target-table WCO lists */
- List *returningLists; /* per-target-table RETURNING tlists */
+ List *returningLists; /* per-target-table RETURNING tlists */
+ List *updateTargetLists; /* per-target-table tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
List *rowMarks; /* PlanRowMarks (non-locking only) */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e41710..eb9d4fc 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -152,6 +152,7 @@ extern bool predicate_refuted_by(List *predicate_list, List *clause_list,
/* in util/tlist.c: */
extern int count_nonjunk_tlist_entries(List *tlist);
+extern List *filter_junk_tlist_entries(List *tlist);
extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 715a24a..21caac3 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -261,7 +261,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
List *resultRelations, List *subpaths,
- List *subroots,
+ List *subroots, List *updateTargetLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
int epqParam);
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a709..66dbde2 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -27,6 +27,8 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
bool inhparent, RelOptInfo *rel);
+extern void make_result_relation_info(PlannerInfo *root,
+ Index rti, Relation relation);
extern List *infer_arbiter_indexes(PlannerInfo *root);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 19c9230..6ee87af 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
#include "nodes/pathnodes.h"
#include "nodes/plannodes.h"
+#include "utils/relcache.h"
/*
@@ -35,7 +36,10 @@ extern Relids get_relids_for_join(Query *query, int joinrelid);
* prototypes for preptlist.c
*/
extern List *preprocess_targetlist(PlannerInfo *root);
-
+/* XXX not really a "prep" function? */
+extern List *make_update_targetlist(PlannerInfo *root,
+ Index rti, Relation relation,
+ List *plan_tlist);
extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
/*
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef..94e43c3 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -545,25 +545,25 @@ create table some_tab_child () inherits (some_tab);
insert into some_tab_child values(1,2);
explain (verbose, costs off)
update some_tab set a = a + 1 where false;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+--------------------------------
Update on public.some_tab
Update on public.some_tab
-> Result
- Output: (a + 1), b, ctid
+ Output: (a + 1), ctid
One-Time Filter: false
(5 rows)
update some_tab set a = a + 1 where false;
explain (verbose, costs off)
update some_tab set a = a + 1 where false returning b, a;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+--------------------------------
Update on public.some_tab
Output: b, a
Update on public.some_tab
-> Result
- Output: (a + 1), b, ctid
+ Output: (a + 1), ctid
One-Time Filter: false
(6 rows)
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 5de53f2..13d0d2a 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1283,12 +1283,12 @@ SELECT * FROM rw_view1;
(4 rows)
EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *;
- QUERY PLAN
--------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------
Update on public.base_tbl
Output: base_tbl.a, base_tbl.b
-> Seq Scan on public.base_tbl
- Output: base_tbl.a, (base_tbl.b + 1), base_tbl.ctid
+ Output: (base_tbl.b + 1), base_tbl.ctid
(4 rows)
UPDATE rw_view1 SET b = b + 1 RETURNING *;
@@ -2309,7 +2309,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
Update on public.t12 t1_2
Update on public.t111 t1_3
-> Index Scan using t1_a_idx on public.t1
- Output: 100, t1.b, t1.c, t1.ctid
+ Output: 100, t1.ctid
Index Cond: ((t1.a > 5) AND (t1.a < 7))
Filter: ((t1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
SubPlan 1
@@ -2325,15 +2325,15 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
-> Seq Scan on public.t111 t12_5
Output: t12_5.a
-> Index Scan using t11_a_idx on public.t11 t1_1
- Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
+ Output: 100, t1_1.ctid
Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
-> Index Scan using t12_a_idx on public.t12 t1_2
- Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
+ Output: 100, t1_2.ctid
Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
-> Index Scan using t111_a_idx on public.t111 t1_3
- Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
+ Output: 100, t1_3.ctid
Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
(33 rows)
@@ -2359,7 +2359,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
Update on public.t12 t1_2
Update on public.t111 t1_3
-> Index Scan using t1_a_idx on public.t1
- Output: (t1.a + 1), t1.b, t1.c, t1.ctid
+ Output: (t1.a + 1), t1.ctid
Index Cond: ((t1.a > 5) AND (t1.a = 8))
Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
SubPlan 1
@@ -2375,15 +2375,15 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
-> Seq Scan on public.t111 t12_5
Output: t12_5.a
-> Index Scan using t11_a_idx on public.t11 t1_1
- Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
+ Output: (t1_1.a + 1), t1_1.ctid
Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
-> Index Scan using t12_a_idx on public.t12 t1_2
- Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
+ Output: (t1_2.a + 1), t1_2.ctid
Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
-> Index Scan using t111_a_idx on public.t111 t1_3
- Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
+ Output: (t1_3.a + 1), t1_3.ctid
Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
(33 rows)
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..dece036 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -172,14 +172,14 @@ EXPLAIN (VERBOSE, COSTS OFF)
UPDATE update_test t
SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a)
WHERE CURRENT_USER = SESSION_USER;
- QUERY PLAN
-------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------
Update on public.update_test t
-> Result
- Output: $1, $2, t.c, (SubPlan 1 (returns $1,$2)), t.ctid
+ Output: $1, $2, (SubPlan 1 (returns $1,$2)), t.ctid
One-Time Filter: (CURRENT_USER = SESSION_USER)
-> Seq Scan on public.update_test t
- Output: t.c, t.a, t.ctid
+ Output: t.a, t.ctid
SubPlan 1 (returns $1,$2)
-> Seq Scan on public.update_test s
Output: s.b, s.a
--
1.8.3.1
v2-0002-Overhaul-how-inherited-update-delete-are-handled.patchapplication/octet-stream; name=v2-0002-Overhaul-how-inherited-update-delete-are-handled.patchDownload
From 98dfc9b33377aaca06bafc26a46dd376e7ee54fe Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Fri, 29 May 2020 21:49:56 +0900
Subject: [PATCH v2 2/8] Overhaul how inherited update/delete are handled
Now that the plan producing tuples to update no longer produces
full tuples, there is no need to make one for each result relation,
just one plan that scans all result relations and funnel them through
an Append, just like the plan for SELECT.
To identify which result relation a tuple to update/delete came from,
the targetlist now contains an additional junk attribute "tableoid".
Considering that the inheritance set may contain foreign tables that
each require a different row-identity junk attribute(s), the plan
needs to emit multiple distinct junk attributes. When transposed
to a child scan node, this targetlist emits a non-NULL value for
the junk attribute that's valid for the child relation and NULL for
others.
Executor can no longer assume any specific order in which the result
relations will be processed. For each tuple to be updated/deleted,
result relation is selected by looking it up in a hash table using
the "tableoid" value as the key.
---
contrib/postgres_fdw/expected/postgres_fdw.out | 414 +++++++------
contrib/postgres_fdw/postgres_fdw.c | 9 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 11 +-
src/backend/commands/explain.c | 6 +-
src/backend/executor/execPartition.c | 114 +---
src/backend/executor/nodeModifyTable.c | 597 +++++++++++--------
src/backend/nodes/outfuncs.c | 18 +
src/backend/optimizer/path/allpaths.c | 22 +-
src/backend/optimizer/plan/createplan.c | 33 +-
src/backend/optimizer/plan/planner.c | 787 +++----------------------
src/backend/optimizer/plan/setrefs.c | 21 +-
src/backend/optimizer/prep/prepjointree.c | 1 -
src/backend/optimizer/prep/preptlist.c | 9 +-
src/backend/optimizer/util/appendinfo.c | 97 +--
src/backend/optimizer/util/inherit.c | 300 +++++++++-
src/backend/optimizer/util/pathnode.c | 2 -
src/backend/optimizer/util/plancat.c | 80 ++-
src/backend/optimizer/util/relnode.c | 31 +-
src/include/executor/executor.h | 4 +-
src/include/nodes/execnodes.h | 24 +-
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 64 +-
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/pathnode.h | 1 -
src/include/optimizer/plancat.h | 6 +-
src/include/optimizer/tlist.h | 2 +
src/test/regress/expected/inherit.out | 22 +-
src/test/regress/expected/partition_join.out | 42 +-
src/test/regress/expected/partition_prune.out | 199 +++----
src/test/regress/expected/rowsecurity.out | 135 ++---
src/test/regress/expected/updatable_views.out | 167 +++---
src/test/regress/expected/update.out | 35 +-
src/test/regress/expected/with.out | 56 +-
33 files changed, 1516 insertions(+), 1797 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 45efaf7..5eafa7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -6300,14 +6300,14 @@ SELECT * FROM foreign_tbl;
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 5;
- QUERY PLAN
-----------------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------
Update on public.parent_tbl
Foreign Update on public.foreign_tbl parent_tbl_1
Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl parent_tbl_1
- Output: (parent_tbl_1.b + 5), parent_tbl_1.ctid, parent_tbl_1.*
- Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
+ Output: (parent_tbl_1.b + 5), parent_tbl_1.ctid, parent_tbl_1.tableoid, parent_tbl_1.*
+ Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b))
(6 rows)
UPDATE rw_view SET b = b + 5; -- should fail
@@ -6315,14 +6315,14 @@ ERROR: new row violates check option for view "rw_view"
DETAIL: Failing row contains (20, 20).
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 15;
- QUERY PLAN
-----------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
Update on public.parent_tbl
Foreign Update on public.foreign_tbl parent_tbl_1
Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl parent_tbl_1
- Output: (parent_tbl_1.b + 15), parent_tbl_1.ctid, parent_tbl_1.*
- Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
+ Output: (parent_tbl_1.b + 15), parent_tbl_1.ctid, parent_tbl_1.tableoid, parent_tbl_1.*
+ Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b))
(6 rows)
UPDATE rw_view SET b = b + 15; -- ok
@@ -7205,36 +7205,22 @@ select * from bar where f1 in (select f1 from foo) for share;
-- Check UPDATE with inherited target and an inherited source table
explain (verbose, costs off)
update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
- QUERY PLAN
----------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
Update on public.bar
- Update on public.bar
- Foreign Update on public.bar2 bar_1
+ Update on public.bar bar_1
+ Foreign Update on public.bar2 bar_2
Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-> Hash Join
- Output: (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
+ Output: (bar.f2 + 100), bar.ctid, foo.ctid, bar.tableoid, bar.*, foo.*, foo.tableoid
Inner Unique: true
Hash Cond: (bar.f1 = foo.f1)
- -> Seq Scan on public.bar
- Output: bar.f2, bar.ctid, bar.f1
- -> Hash
- Output: foo.ctid, foo.f1, foo.*, foo.tableoid
- -> HashAggregate
- Output: foo.ctid, foo.f1, foo.*, foo.tableoid
- Group Key: foo.f1
- -> Append
- -> Seq Scan on public.foo foo_1
- Output: foo_1.ctid, foo_1.f1, foo_1.*, foo_1.tableoid
- -> Foreign Scan on public.foo2 foo_2
- Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid
- Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
- -> Hash Join
- Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, foo.ctid, foo.*, foo.tableoid
- Inner Unique: true
- Hash Cond: (bar_1.f1 = foo.f1)
- -> Foreign Scan on public.bar2 bar_1
- Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
- Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Append
+ -> Seq Scan on public.bar bar_1
+ Output: bar_1.f2, bar_1.ctid, bar_1.f1, bar_1.tableoid, bar_1.*
+ -> Foreign Scan on public.bar2 bar_2
+ Output: bar_2.f2, bar_2.ctid, bar_2.f1, bar_2.tableoid, bar_2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2
-> Hash
Output: foo.ctid, foo.f1, foo.*, foo.tableoid
-> HashAggregate
@@ -7246,7 +7232,7 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
-> Foreign Scan on public.foo2 foo_2
Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
-(39 rows)
+(25 rows)
update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
select tableoid::regclass, * from bar order by 1,2;
@@ -7266,39 +7252,24 @@ update bar set f2 = f2 + 100
from
( select f1 from foo union all select f1+3 from foo ) ss
where bar.f1 = ss.f1;
- QUERY PLAN
---------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
Update on public.bar
- Update on public.bar
- Foreign Update on public.bar2 bar_1
+ Update on public.bar bar_1
+ Foreign Update on public.bar2 bar_2
Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
- -> Hash Join
- Output: (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
- Hash Cond: (foo.f1 = bar.f1)
- -> Append
- -> Seq Scan on public.foo
- Output: ROW(foo.f1), foo.f1
- -> Foreign Scan on public.foo2 foo_1
- Output: ROW(foo_1.f1), foo_1.f1
- Remote SQL: SELECT f1 FROM public.loct1
- -> Seq Scan on public.foo foo_2
- Output: ROW((foo_2.f1 + 3)), (foo_2.f1 + 3)
- -> Foreign Scan on public.foo2 foo_3
- Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3)
- Remote SQL: SELECT f1 FROM public.loct1
- -> Hash
- Output: bar.f2, bar.ctid, bar.f1
- -> Seq Scan on public.bar
- Output: bar.f2, bar.ctid, bar.f1
-> Merge Join
- Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, (ROW(foo.f1))
- Merge Cond: (bar_1.f1 = foo.f1)
+ Output: (bar.f2 + 100), bar.ctid, (ROW(foo.f1)), bar.tableoid, bar.*
+ Merge Cond: (bar.f1 = foo.f1)
-> Sort
- Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
- Sort Key: bar_1.f1
- -> Foreign Scan on public.bar2 bar_1
- Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
- Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ Output: bar.f2, bar.ctid, bar.f1, bar.tableoid, bar.*
+ Sort Key: bar.f1
+ -> Append
+ -> Seq Scan on public.bar bar_1
+ Output: bar_1.f2, bar_1.ctid, bar_1.f1, bar_1.tableoid, bar_1.*
+ -> Foreign Scan on public.bar2 bar_2
+ Output: bar_2.f2, bar_2.ctid, bar_2.f1, bar_2.tableoid, bar_2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2
-> Sort
Output: (ROW(foo.f1)), foo.f1
Sort Key: foo.f1
@@ -7313,7 +7284,7 @@ where bar.f1 = ss.f1;
-> Foreign Scan on public.foo2 foo_3
Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3)
Remote SQL: SELECT f1 FROM public.loct1
-(45 rows)
+(30 rows)
update bar set f2 = f2 + 100
from
@@ -7439,18 +7410,21 @@ ERROR: WHERE CURRENT OF is not supported for this table type
rollback;
explain (verbose, costs off)
delete from foo where f1 < 5 returning *;
- QUERY PLAN
---------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------
Delete on public.foo
- Output: foo.f1, foo.f2
- Delete on public.foo
- Foreign Delete on public.foo2 foo_1
- -> Index Scan using i_foo_f1 on public.foo
- Output: foo.ctid
- Index Cond: (foo.f1 < 5)
- -> Foreign Delete on public.foo2 foo_1
- Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2
-(9 rows)
+ Output: foo_1.f1, foo_1.f2
+ Delete on public.foo foo_1
+ Foreign Delete on public.foo2 foo_2
+ Remote SQL: DELETE FROM public.loct1 WHERE ctid = $1 RETURNING f1, f2
+ -> Append
+ -> Index Scan using i_foo_f1 on public.foo foo_1
+ Output: foo_1.ctid, foo_1.tableoid
+ Index Cond: (foo_1.f1 < 5)
+ -> Foreign Scan on public.foo2 foo_2
+ Output: foo_2.ctid, foo_2.tableoid
+ Remote SQL: SELECT ctid FROM public.loct1 WHERE ((f1 < 5))
+(12 rows)
delete from foo where f1 < 5 returning *;
f1 | f2
@@ -7464,17 +7438,22 @@ delete from foo where f1 < 5 returning *;
explain (verbose, costs off)
update bar set f2 = f2 + 100 returning *;
- QUERY PLAN
-------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------
Update on public.bar
- Output: bar.f1, bar.f2
- Update on public.bar
- Foreign Update on public.bar2 bar_1
- -> Seq Scan on public.bar
- Output: (bar.f2 + 100), bar.ctid
- -> Foreign Update on public.bar2 bar_1
- Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2
-(8 rows)
+ Output: bar_1.f1, bar_1.f2
+ Update on public.bar bar_1
+ Foreign Update on public.bar2 bar_2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2
+ -> Result
+ Output: (bar.f2 + 100), bar.ctid, bar.tableoid, bar.*
+ -> Append
+ -> Seq Scan on public.bar bar_1
+ Output: bar_1.f2, bar_1.ctid, bar_1.tableoid, bar_1.*
+ -> Foreign Scan on public.bar2 bar_2
+ Output: bar_2.f2, bar_2.ctid, bar_2.tableoid, bar_2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2
+(13 rows)
update bar set f2 = f2 + 100 returning *;
f1 | f2
@@ -7499,15 +7478,18 @@ update bar set f2 = f2 + 100;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Update on public.bar
- Update on public.bar
- Foreign Update on public.bar2 bar_1
+ Update on public.bar bar_1
+ Foreign Update on public.bar2 bar_2
Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ctid = $1 RETURNING f1, f2, f3
- -> Seq Scan on public.bar
- Output: (bar.f2 + 100), bar.ctid
- -> Foreign Scan on public.bar2 bar_1
- Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*
- Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-(9 rows)
+ -> Result
+ Output: (bar.f2 + 100), bar.ctid, bar.tableoid, bar.*
+ -> Append
+ -> Seq Scan on public.bar bar_1
+ Output: bar_1.f2, bar_1.ctid, bar_1.tableoid, bar_1.*
+ -> Foreign Scan on public.bar2 bar_2
+ Output: bar_2.f2, bar_2.ctid, bar_2.tableoid, bar_2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2
+(12 rows)
update bar set f2 = f2 + 100;
NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2
@@ -7524,19 +7506,20 @@ NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2
NOTICE: OLD: (7,277,77),NEW: (7,377,77)
explain (verbose, costs off)
delete from bar where f2 < 400;
- QUERY PLAN
----------------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------
Delete on public.bar
- Delete on public.bar
- Foreign Delete on public.bar2 bar_1
+ Delete on public.bar bar_1
+ Foreign Delete on public.bar2 bar_2
Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 RETURNING f1, f2, f3
- -> Seq Scan on public.bar
- Output: bar.ctid
- Filter: (bar.f2 < 400)
- -> Foreign Scan on public.bar2 bar_1
- Output: bar_1.ctid, bar_1.*
- Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
-(10 rows)
+ -> Append
+ -> Seq Scan on public.bar bar_1
+ Output: bar_1.ctid, bar_1.tableoid, bar_1.*
+ Filter: (bar_1.f2 < 400)
+ -> Foreign Scan on public.bar2 bar_2
+ Output: bar_2.ctid, bar_2.tableoid, bar_2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400))
+(11 rows)
delete from bar where f2 < 400;
NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON bar2
@@ -7567,23 +7550,28 @@ analyze remt1;
analyze remt2;
explain (verbose, costs off)
update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *;
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------
Update on public.parent
- Output: parent.a, parent.b, remt2.a, remt2.b
- Update on public.parent
- Foreign Update on public.remt1 parent_1
+ Output: parent_1.a, parent_1.b, remt2.a, remt2.b
+ Update on public.parent parent_1
+ Foreign Update on public.remt1 parent_2
+ Remote SQL: UPDATE public.loct1 SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Nested Loop
- Output: (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b
+ Output: (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b, parent.tableoid, parent.*
Join Filter: (parent.a = remt2.a)
- -> Seq Scan on public.parent
- Output: parent.b, parent.ctid, parent.a
- -> Foreign Scan on public.remt2
+ -> Append
+ -> Seq Scan on public.parent parent_1
+ Output: parent_1.b, parent_1.ctid, parent_1.a, parent_1.tableoid, parent_1.*
+ -> Foreign Scan on public.remt1 parent_2
+ Output: parent_2.b, parent_2.ctid, parent_2.a, parent_2.tableoid, parent_2.*
+ Remote SQL: SELECT a, b, ctid FROM public.loct1
+ -> Materialize
Output: remt2.b, remt2.*, remt2.a
- Remote SQL: SELECT a, b FROM public.loct2
- -> Foreign Update
- Remote SQL: UPDATE public.loct1 r4 SET b = (r4.b || r2.b) FROM public.loct2 r2 WHERE ((r4.a = r2.a)) RETURNING r4.a, r4.b, r2.a, r2.b
-(14 rows)
+ -> Foreign Scan on public.remt2
+ Output: remt2.b, remt2.*, remt2.a
+ Remote SQL: SELECT a, b FROM public.loct2
+(19 rows)
update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *;
a | b | a | b
@@ -7594,23 +7582,28 @@ update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a re
explain (verbose, costs off)
delete from parent using remt2 where parent.a = remt2.a returning parent;
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------
Delete on public.parent
- Output: parent.*
- Delete on public.parent
- Foreign Delete on public.remt1 parent_1
+ Output: parent_1.*
+ Delete on public.parent parent_1
+ Foreign Delete on public.remt1 parent_2
+ Remote SQL: DELETE FROM public.loct1 WHERE ctid = $1 RETURNING a, b
-> Nested Loop
- Output: parent.ctid, remt2.*
+ Output: parent.ctid, remt2.*, parent.tableoid
Join Filter: (parent.a = remt2.a)
- -> Seq Scan on public.parent
- Output: parent.ctid, parent.a
- -> Foreign Scan on public.remt2
+ -> Append
+ -> Seq Scan on public.parent parent_1
+ Output: parent_1.ctid, parent_1.a, parent_1.tableoid
+ -> Foreign Scan on public.remt1 parent_2
+ Output: parent_2.ctid, parent_2.a, parent_2.tableoid
+ Remote SQL: SELECT a, ctid FROM public.loct1
+ -> Materialize
Output: remt2.*, remt2.a
- Remote SQL: SELECT a, b FROM public.loct2
- -> Foreign Delete
- Remote SQL: DELETE FROM public.loct1 r4 USING public.loct2 r2 WHERE ((r4.a = r2.a)) RETURNING r4.a, r4.b
-(14 rows)
+ -> Foreign Scan on public.remt2
+ Output: remt2.*, remt2.a
+ Remote SQL: SELECT a, b FROM public.loct2
+(19 rows)
delete from parent using remt2 where parent.a = remt2.a returning parent;
parent
@@ -7782,36 +7775,40 @@ select tableoid::regclass, * FROM locp;
locp | 2 | qux
(1 row)
--- It's not allowed to move a row from a partition that is foreign to another
-update utrtest set a = 2 where b = 'foo' returning *;
+-- It's not allowed to move a row from a foreign partition to a local partition
+update utrtest set a = 2 where a = 1 returning *;
ERROR: new row for relation "loct" violates check constraint "loct_a_check"
DETAIL: Failing row contains (2, foo).
-CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
--- But the reverse is allowed
-update utrtest set a = 1 where b = 'qux' returning *;
+CONTEXT: remote SQL command: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
+-- But the reverse is allowed, if the foreign partition itself is not to be updated
+update utrtest set a = 1 where a = 2 returning *;
a | b
---+-----
1 | qux
(1 row)
+delete from remp where b = 'qux';
+insert into utrtest values (2, 'qux');
+update utrtest set a = 1 returning *;
+ERROR: cannot route tuples into foreign table to be updated "remp"
select tableoid::regclass, * FROM utrtest;
tableoid | a | b
----------+---+-----
remp | 1 | foo
- remp | 1 | qux
+ locp | 2 | qux
(2 rows)
select tableoid::regclass, * FROM remp;
tableoid | a | b
----------+---+-----
remp | 1 | foo
- remp | 1 | qux
-(2 rows)
+(1 row)
select tableoid::regclass, * FROM locp;
- tableoid | a | b
-----------+---+---
-(0 rows)
+ tableoid | a | b
+----------+---+-----
+ locp | 2 | qux
+(1 row)
-- The executor should not let unexercised FDWs shut down
update utrtest set a = 1 where b = 'foo';
@@ -7823,38 +7820,37 @@ insert into utrtest values (2, 'qux');
-- Check case where the foreign partition is a subplan target rel
explain (verbose, costs off)
update utrtest set a = 1 where a = 1 or a = 2 returning *;
- QUERY PLAN
-----------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
Foreign Update on public.remp utrtest_1
+ Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
Update on public.locp utrtest_2
- -> Foreign Update on public.remp utrtest_1
- Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b
- -> Seq Scan on public.locp utrtest_2
- Output: 1, utrtest_2.ctid
- Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
-(9 rows)
+ -> Append
+ -> Foreign Scan on public.remp utrtest_1
+ Output: 1, utrtest_1.ctid, utrtest_1.tableoid, utrtest_1.*
+ Remote SQL: SELECT a, b, ctid FROM public.loct WHERE (((a = 1) OR (a = 2)))
+ -> Seq Scan on public.locp utrtest_2
+ Output: 1, utrtest_2.ctid, utrtest_2.tableoid, utrtest_2.*
+ Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
+(12 rows)
-- The new values are concatenated with ' triggered !'
update utrtest set a = 1 where a = 1 or a = 2 returning *;
- a | b
----+-----------------
- 1 | qux triggered !
-(1 row)
-
+ERROR: cannot route tuples into foreign table to be updated "remp"
delete from utrtest;
insert into utrtest values (2, 'qux');
-- Check case where the foreign partition isn't a subplan target rel
explain (verbose, costs off)
update utrtest set a = 1 where a = 2 returning *;
- QUERY PLAN
------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
Update on public.locp utrtest_1
-> Seq Scan on public.locp utrtest_1
- Output: 1, utrtest_1.ctid
+ Output: 1, utrtest_1.ctid, utrtest_1.tableoid
Filter: (utrtest_1.a = 2)
(6 rows)
@@ -7875,66 +7871,53 @@ insert into utrtest values (2, 'qux');
-- with a direct modification plan
explain (verbose, costs off)
update utrtest set a = 1 returning *;
- QUERY PLAN
------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
Foreign Update on public.remp utrtest_1
+ Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
Update on public.locp utrtest_2
- -> Foreign Update on public.remp utrtest_1
- Remote SQL: UPDATE public.loct SET a = 1 RETURNING a, b
- -> Seq Scan on public.locp utrtest_2
- Output: 1, utrtest_2.ctid
-(8 rows)
+ -> Append
+ -> Foreign Scan on public.remp utrtest_1
+ Output: 1, utrtest_1.ctid, utrtest_1.tableoid, utrtest_1.*
+ Remote SQL: SELECT a, b, ctid FROM public.loct
+ -> Seq Scan on public.locp utrtest_2
+ Output: 1, utrtest_2.ctid, utrtest_2.tableoid, utrtest_2.*
+(11 rows)
update utrtest set a = 1 returning *;
- a | b
----+-----
- 1 | foo
- 1 | qux
-(2 rows)
-
+ERROR: cannot route tuples into foreign table to be updated "remp"
delete from utrtest;
insert into utrtest values (1, 'foo');
insert into utrtest values (2, 'qux');
-- with a non-direct modification plan
explain (verbose, costs off)
update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
- QUERY PLAN
-----------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1
Foreign Update on public.remp utrtest_1
Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
Update on public.locp utrtest_2
-> Hash Join
- Output: 1, utrtest_1.ctid, utrtest_1.*, "*VALUES*".*, "*VALUES*".column1
- Hash Cond: (utrtest_1.a = "*VALUES*".column1)
- -> Foreign Scan on public.remp utrtest_1
- Output: utrtest_1.ctid, utrtest_1.*, utrtest_1.a
- Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
- -> Hash
- Output: "*VALUES*".*, "*VALUES*".column1
- -> Values Scan on "*VALUES*"
- Output: "*VALUES*".*, "*VALUES*".column1
- -> Hash Join
- Output: 1, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1
- Hash Cond: (utrtest_2.a = "*VALUES*".column1)
- -> Seq Scan on public.locp utrtest_2
- Output: utrtest_2.ctid, utrtest_2.a
+ Output: 1, utrtest.ctid, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.*
+ Hash Cond: (utrtest.a = "*VALUES*".column1)
+ -> Append
+ -> Foreign Scan on public.remp utrtest_1
+ Output: utrtest_1.ctid, utrtest_1.a, utrtest_1.tableoid, utrtest_1.*
+ Remote SQL: SELECT a, b, ctid FROM public.loct
+ -> Seq Scan on public.locp utrtest_2
+ Output: utrtest_2.ctid, utrtest_2.a, utrtest_2.tableoid, utrtest_2.*
-> Hash
Output: "*VALUES*".*, "*VALUES*".column1
-> Values Scan on "*VALUES*"
Output: "*VALUES*".*, "*VALUES*".column1
-(24 rows)
+(18 rows)
update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
- a | b | x
----+-----+---
- 1 | foo | 1
- 1 | qux | 2
-(2 rows)
-
+ERROR: cannot route tuples into foreign table to be updated "remp"
-- Change the definition of utrtest so that the foreign partition get updated
-- after the local partition
delete from utrtest;
@@ -7950,50 +7933,47 @@ insert into utrtest values (3, 'xyzzy');
-- with a direct modification plan
explain (verbose, costs off)
update utrtest set a = 3 returning *;
- QUERY PLAN
------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
Update on public.locp utrtest_1
Foreign Update on public.remp utrtest_2
- -> Seq Scan on public.locp utrtest_1
- Output: 3, utrtest_1.ctid
- -> Foreign Update on public.remp utrtest_2
- Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b
-(8 rows)
+ Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
+ -> Append
+ -> Seq Scan on public.locp utrtest_1
+ Output: 3, utrtest_1.ctid, utrtest_1.tableoid, utrtest_1.*
+ -> Foreign Scan on public.remp utrtest_2
+ Output: 3, utrtest_2.ctid, utrtest_2.tableoid, utrtest_2.*
+ Remote SQL: SELECT a, b, ctid FROM public.loct
+(11 rows)
update utrtest set a = 3 returning *; -- ERROR
ERROR: cannot route tuples into foreign table to be updated "remp"
-- with a non-direct modification plan
explain (verbose, costs off)
update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *;
- QUERY PLAN
-----------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1
Update on public.locp utrtest_1
Foreign Update on public.remp utrtest_2
Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
-> Hash Join
- Output: 3, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1
- Hash Cond: (utrtest_1.a = "*VALUES*".column1)
- -> Seq Scan on public.locp utrtest_1
- Output: utrtest_1.ctid, utrtest_1.a
- -> Hash
- Output: "*VALUES*".*, "*VALUES*".column1
- -> Values Scan on "*VALUES*"
- Output: "*VALUES*".*, "*VALUES*".column1
- -> Hash Join
- Output: 3, utrtest_2.ctid, utrtest_2.*, "*VALUES*".*, "*VALUES*".column1
- Hash Cond: (utrtest_2.a = "*VALUES*".column1)
- -> Foreign Scan on public.remp utrtest_2
- Output: utrtest_2.ctid, utrtest_2.*, utrtest_2.a
- Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
+ Output: 3, utrtest.ctid, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.*
+ Hash Cond: (utrtest.a = "*VALUES*".column1)
+ -> Append
+ -> Seq Scan on public.locp utrtest_1
+ Output: utrtest_1.ctid, utrtest_1.a, utrtest_1.tableoid, utrtest_1.*
+ -> Foreign Scan on public.remp utrtest_2
+ Output: utrtest_2.ctid, utrtest_2.a, utrtest_2.tableoid, utrtest_2.*
+ Remote SQL: SELECT a, b, ctid FROM public.loct
-> Hash
Output: "*VALUES*".*, "*VALUES*".column1
-> Values Scan on "*VALUES*"
Output: "*VALUES*".*, "*VALUES*".column1
-(24 rows)
+(18 rows)
update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR
ERROR: cannot route tuples into foreign table to be updated "remp"
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 16c5f19..697ec68 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1825,7 +1825,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
rte,
resultRelInfo,
mtstate->operation,
- mtstate->mt_plans[subplan_index]->plan,
+ mtstate->mt_plans[0]->plan,
query,
target_attrs,
has_returning,
@@ -1938,8 +1938,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
*/
if (plan && plan->operation == CMD_UPDATE &&
(resultRelInfo->ri_usesFdwDirectModify ||
- resultRelInfo->ri_FdwState) &&
- resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan)
+ resultRelInfo->ri_FdwState))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route tuples into foreign table to be updated \"%s\"",
@@ -2166,8 +2165,8 @@ postgresPlanDirectModify(PlannerInfo *root,
* It's unsafe to modify a foreign table directly if there are any local
* joins needed.
*/
- subplan = (Plan *) list_nth(plan->plans, subplan_index);
- resultInfo = linitial(root->result_rel_list);
+ subplan = (Plan *) linitial(plan->plans);
+ resultInfo = root->result_rel_array[resultRelation];
Assert(resultInfo != NULL);
if (!IsA(subplan, ForeignScan))
return false;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8397166..5669097 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2043,11 +2043,14 @@ select tableoid::regclass, * FROM utrtest;
select tableoid::regclass, * FROM remp;
select tableoid::regclass, * FROM locp;
--- It's not allowed to move a row from a partition that is foreign to another
-update utrtest set a = 2 where b = 'foo' returning *;
+-- It's not allowed to move a row from a foreign partition to a local partition
+update utrtest set a = 2 where a = 1 returning *;
--- But the reverse is allowed
-update utrtest set a = 1 where b = 'qux' returning *;
+-- But the reverse is allowed, if the foreign partition itself is not to be updated
+update utrtest set a = 1 where a = 2 returning *;
+delete from remp where b = 'qux';
+insert into utrtest values (2, 'qux');
+update utrtest set a = 1 returning *;
select tableoid::regclass, * FROM utrtest;
select tableoid::regclass, * FROM remp;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 093864c..37e7ae1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3667,14 +3667,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
}
/* Should we explicitly label target relations? */
- labeltargets = (mtstate->mt_nplans > 1 ||
- (mtstate->mt_nplans == 1 &&
+ labeltargets = (mtstate->mt_nrels > 1 ||
+ (mtstate->mt_nrels == 1 &&
mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
- for (j = 0; j < mtstate->mt_nplans; j++)
+ for (j = 0; j < mtstate->mt_nrels; j++)
{
ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..24afcb2 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -94,7 +95,6 @@ struct PartitionTupleRouting
ResultRelInfo **partitions;
int num_partitions;
int max_partitions;
- HTAB *subplan_resultrel_htab;
MemoryContext memcxt;
};
@@ -147,16 +147,7 @@ typedef struct PartitionDispatchData
int indexes[FLEXIBLE_ARRAY_MEMBER];
} PartitionDispatchData;
-/* struct to hold result relations coming from UPDATE subplans */
-typedef struct SubplanResultRelHashElem
-{
- Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
-} SubplanResultRelHashElem;
-
-static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
- PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
EState *estate, PartitionTupleRouting *proute,
PartitionDispatch dispatch,
@@ -212,7 +203,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -234,17 +224,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -352,7 +331,7 @@ ExecFindPartition(ModifyTableState *mtstate,
if (partdesc->is_leaf[partidx])
{
- ResultRelInfo *rri;
+ ResultRelInfo *rri = NULL;
/*
* Look to see if we've already got a ResultRelInfo for this
@@ -366,28 +345,32 @@ ExecFindPartition(ModifyTableState *mtstate,
}
else
{
- bool found = false;
-
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if we might be able to find one in the mtstate's
+ * result relations array.
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate && mtstate->mt_subplan_resultrel_hash)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
+ int whichrel; /* unused */
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupModifyResultRelByOid(mtstate, partoid,
+ &whichrel);
+ if (rri)
{
- found = true;
- rri = elem->rri;
-
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
+ /*
+ * This is required in order to convert the
+ * partition's tuple to be compatible with the root
+ * partitioned table's tuple descriptor. When
+ * generating the per-subplan result rels, this would
+ * not have been set.
+ */
+ rri->ri_PartitionRoot = proute->partition_root;
+
/* Set up the PartitionRoutingInfo for it */
ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
rri, partidx);
@@ -395,7 +378,7 @@ ExecFindPartition(ModifyTableState *mtstate,
}
/* We need to create a new one. */
- if (!found)
+ if (rri == NULL)
rri = ExecInitPartitionInfo(mtstate, estate, proute,
dispatch,
rootResultRelInfo, partidx);
@@ -447,51 +430,6 @@ ExecFindPartition(ModifyTableState *mtstate,
}
/*
- * ExecHashSubPlanResultRelsByOid
- * Build a hash table to allow fast lookups of subplan ResultRelInfos by
- * partition Oid. We also populate the subplan ResultRelInfo with an
- * ri_PartitionRoot.
- */
-static void
-ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
- PartitionTupleRouting *proute)
-{
- HASHCTL ctl;
- HTAB *htab;
- int i;
-
- memset(&ctl, 0, sizeof(ctl));
- ctl.keysize = sizeof(Oid);
- ctl.entrysize = sizeof(SubplanResultRelHashElem);
- ctl.hcxt = CurrentMemoryContext;
-
- htab = hash_create("PartitionTupleRouting table", mtstate->mt_nplans,
- &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
- proute->subplan_resultrel_htab = htab;
-
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
- {
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
- bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
- SubplanResultRelHashElem *elem;
-
- elem = (SubplanResultRelHashElem *)
- hash_search(htab, &partoid, HASH_ENTER, &found);
- Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
- }
-}
-
-/*
* ExecInitPartitionInfo
* Lock the partition and initialize ResultRelInfo. Also setup other
* information for the partition and store it in the next empty slot in
@@ -550,7 +488,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse with ExecLookupModifyResultRelByOid().
*/
if (node && node->withCheckOptionLists != NIL)
{
@@ -566,10 +504,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
Assert((node->operation == CMD_INSERT &&
list_length(node->withCheckOptionLists) == 1 &&
- list_length(node->plans) == 1) ||
+ list_length(node->resultRelations) == 1) ||
(node->operation == CMD_UPDATE &&
list_length(node->withCheckOptionLists) ==
- list_length(node->plans)));
+ list_length(node->resultRelations)));
/*
* Use the WCO list of the first plan as a reference to calculate
@@ -614,7 +552,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* build the returningList for partitions within the planner, but simple
* translation of varattnos will suffice. This only occurs for the INSERT
* case or in the case of UPDATE tuple routing where we didn't find a
- * result rel to reuse in ExecSetupPartitionTupleRouting().
+ * result rel to reuse with ExecLookupModifyResultRelByOid().
*/
if (node && node->returningLists != NIL)
{
@@ -626,10 +564,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
list_length(node->returningLists) == 1 &&
- list_length(node->plans) == 1) ||
+ list_length(node->resultRelations) == 1) ||
(node->operation == CMD_UPDATE &&
list_length(node->returningLists) ==
- list_length(node->plans)));
+ list_length(node->resultRelations)));
/*
* Use the RETURNING list of the first plan as a reference to
@@ -1096,7 +1034,7 @@ void
ExecCleanupTupleRouting(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
- HTAB *htab = proute->subplan_resultrel_htab;
+ HTAB *htab = mtstate->mt_subplan_resultrel_hash;
int i;
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0a99818..74c12be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1045,8 +1045,84 @@ ldelete:;
return NULL;
}
+/* struct to hold info about UPDATE/DELETE result relations */
+typedef struct SubplanResultRelHashElem
+{
+ Oid relid; /* hash key -- must be first */
+ int whichrel; /* index into mtstate->resultRelInfo */
+} SubplanResultRelHashElem;
+
+/*
+ * ExecHashSubPlanResultRelsByOid
+ * Build a hash table to allow fast lookups of subplan ResultRelInfos by
+ * result relation OIDs.
+ */
+static void
+ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate)
+{
+ HASHCTL ctl;
+ HTAB *htab;
+ int i;
+
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(SubplanResultRelHashElem);
+ ctl.hcxt = CurrentMemoryContext;
+
+ Assert(mtstate->mt_subplan_resultrel_hash == NULL);
+
+ htab = hash_create("ModifyTable result relations", mtstate->mt_nrels,
+ &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ mtstate->mt_subplan_resultrel_hash = htab;
+
+ /* Hash all subplans by their Oid */
+ for (i = 0; i < mtstate->mt_nrels; i++)
+ {
+ ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ bool found;
+ Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
+ SubplanResultRelHashElem *elem;
+
+ elem = (SubplanResultRelHashElem *)
+ hash_search(htab, &partoid, HASH_ENTER, &found);
+ Assert(!found);
+ elem->whichrel = i;
+ }
+}
+
+/*
+ * ExecLookupUpdateResultRelByOid
+ * Maps a given OID to ResultRelInfo of a result relation managed by this
+ * ModifyTableState node
+ *
+ * *whichrel is set to relative position of the result relation in the list
+ * of result relations managed by this node.
+ *
+ * NULL is returned if the OID does not map to any result relation, which is
+ * possible when called by ExecFindPartition().
+ */
+ResultRelInfo *
+ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
+ Oid reloid, int *whichrel)
+{
+ SubplanResultRelHashElem *elem;
+
+ Assert(mtstate->mt_subplan_resultrel_hash != NULL);
+ elem = hash_search(mtstate->mt_subplan_resultrel_hash, &reloid,
+ HASH_FIND, NULL);
+
+ *whichrel = -1;
+ if (elem)
+ {
+ *whichrel = elem->whichrel;
+ return mtstate->resultRelInfo + elem->whichrel;
+ }
+
+ return NULL;
+}
+
/*
- * ExecProjectNewInsertTuple
+ * ExecGetInsertNewTuple
* This prepares a "new" tuple ready to be put into a result relation
* by removing any junk columns of the tuple produced by a plan.
*/
@@ -1067,7 +1143,7 @@ ExecGetNewInsertTuple(ResultRelInfo *relinfo,
/*
- * ExecProjectNewUpdateTuple
+ * ExecGetUpdateNewTuple
* This prepares a "new" tuple ready to be put into a result relation
* by combining the "plan" tuple and the "old" tuple.
*
@@ -1355,7 +1431,7 @@ lreplace:;
* position of the resultRel in mtstate->resultRelInfo[].
*/
map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
+ Assert(map_index >= 0 && map_index < mtstate->mt_nrels);
tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
@@ -2032,7 +2108,7 @@ ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
TupleDesc outdesc;
- int numResultRelInfos = mtstate->mt_nplans;
+ int numResultRelInfos = mtstate->mt_nrels;
int i;
/*
@@ -2065,7 +2141,7 @@ tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
if (mtstate->mt_per_subplan_tupconv_maps == NULL)
ExecSetupChildParentMapForSubplan(mtstate);
- Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
+ Assert(whichplan >= 0 && whichplan < mtstate->mt_nrels);
return mtstate->mt_per_subplan_tupconv_maps[whichplan];
}
@@ -2126,7 +2202,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+ resultRelInfo = node->resultRelInfo + node->mt_whichrel;
subplanstate = node->mt_plans[node->mt_whichplan];
/*
@@ -2164,41 +2240,53 @@ ExecModifyTable(PlanState *pstate)
planSlot = ExecProcNode(subplanstate);
+ /* No more tuples to process? */
if (TupIsNull(planSlot))
+ break;
+
+ /* Select the result relation to operate on. */
+ if (node->mt_nrels > 1)
{
- /* advance to next subplan if any */
- node->mt_whichplan++;
- if (node->mt_whichplan < node->mt_nplans)
+ bool isNull;
+ Datum datum;
+ Oid tableoid;
+
+ /* Table OID must be present in the plan's targetlist. */
+ Assert(AttributeNumberIsValid(node->mt_tableOidAttno));
+ datum = ExecGetJunkAttribute(planSlot, node->mt_tableOidAttno,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "tableoid is NULL");
+ tableoid = DatumGetObjectId(datum);
+ Assert(OidIsValid(tableoid));
+
+ /* Table OID -> ResultRelInfo. */
+ resultRelInfo = ExecLookupModifyResultRelByOid(node, tableoid,
+ &node->mt_whichrel);
+ Assert(node->mt_whichrel >= 0 &&
+ node->mt_whichrel < node->mt_nrels);
+ estate->es_result_relation_info = resultRelInfo;
+
+ /* Prepare to convert transition tuples from this child. */
+ if (node->mt_transition_capture != NULL)
{
- resultRelInfo++;
- subplanstate = node->mt_plans[node->mt_whichplan];
- estate->es_result_relation_info = resultRelInfo;
- EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
- node->mt_arowmarks[node->mt_whichplan]);
- /* Prepare to convert transition tuples from this child. */
- if (node->mt_transition_capture != NULL)
- {
- node->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichplan);
- }
- if (node->mt_oc_transition_capture != NULL)
- {
- node->mt_oc_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichplan);
- }
- continue;
+ node->mt_transition_capture->tcs_map =
+ tupconv_map_for_subplan(node, node->mt_whichrel);
+ }
+ if (node->mt_oc_transition_capture != NULL)
+ {
+ node->mt_oc_transition_capture->tcs_map =
+ tupconv_map_for_subplan(node, node->mt_whichrel);
}
- else
- break;
}
/*
* Ensure input tuple is the right format for the target relation.
*/
- if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
+ if (node->mt_scans[node->mt_whichrel]->tts_ops != planSlot->tts_ops)
{
- ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot);
- planSlot = node->mt_scans[node->mt_whichplan];
+ ExecCopySlot(node->mt_scans[node->mt_whichrel], planSlot);
+ planSlot = node->mt_scans[node->mt_whichrel];
}
/*
@@ -2361,9 +2449,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
+ int nrels = list_length(node->resultRelations);
ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo;
Plan *subplan;
+ TupleDesc plan_result_type;
ListCell *l;
int i;
Relation rel;
@@ -2386,7 +2476,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
- mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nrels);
/* If modifying a partitioned table, initialize the root table info */
if (node->rootResultRelIndex >= 0)
@@ -2400,23 +2490,73 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
mtstate->fireBSTriggers = true;
+ mtstate->mt_nrels = nrels;
+ mtstate->mt_whichrel = 0;
+
/*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries. Note we *must* set
- * estate->es_result_relation_info correctly while we initialize each
- * sub-plan; external modules such as FDWs may depend on that (see
- * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one
- * example).
+ * Call ExecInitNode on the only plan to be executed and save the result
+ * into the array "mt_plans".
+ *
+ * Note we *must* set es_result_relation_info correctly while we
+ * initialize the plan; external modules such as FDWs may depend on that
+ * (see contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify()
+ * as one example).
*/
saved_resultRelInfo = estate->es_result_relation_info;
+ estate->es_result_relation_info = mtstate->resultRelInfo;
+ subplan = linitial(node->plans);
+ mtstate->mt_plans[0] = ExecInitNode(subplan, estate, eflags);
+ estate->es_result_relation_info = saved_resultRelInfo;
+ plan_result_type = ExecGetResultType(mtstate->mt_plans[0]);
+ if (mtstate->mt_nrels > 1)
+ {
+ /*
+ * There must be "tableoid" junk attribute present if there are
+ * multiple result relations to update or delete from.
+ */
+ mtstate->mt_tableOidAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
+ Assert(AttributeNumberIsValid(mtstate->mt_tableOidAttno));
+ }
+
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
+ /*
+ * Per result relation initializations.
+ * TODO: do this lazily.
+ */
resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->plans)
+ for (i = 0; i < nrels; i++)
{
- subplan = (Plan *) lfirst(l);
+ List *resultTargetList = NIL;
+ bool need_projection = false;
/* Initialize the usesFdwDirectModify flag */
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
@@ -2452,13 +2592,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
operation == CMD_UPDATE)
update_tuple_routing_needed = true;
- /* Now init the plan for this result rel */
- estate->es_result_relation_info = resultRelInfo;
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
+ ExecInitExtraTupleSlot(mtstate->ps.state, plan_result_type,
table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ /* Now init the plan for this result rel */
+ estate->es_result_relation_info = resultRelInfo;
+
/* Also let FDWs init themselves for foreign-table result rels */
if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
@@ -2473,12 +2613,146 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
eflags);
}
+ /*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ if (node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists, i);
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists, i);
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /*
+ * Prepare to generate tuples suitable for the target relation.
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ if (operation == CMD_INSERT)
+ {
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (!tle->resjunk)
+ resultTargetList = lappend(resultTargetList, tle);
+ else
+ need_projection = true;
+ }
+ }
+ else
+ {
+ resultTargetList = (List *) list_nth(node->updateTargetLists,
+ i);
+ need_projection = true;
+ }
+
+ /*
+ * The clean list must produce a tuple suitable for the result
+ * relation.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ resultTargetList);
+ }
+
+ if (need_projection)
+ {
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ /*
+ * For UPDATE, we use the old tuple to fill up missing values in
+ * the tuple produced by the plan to get the new tuple.
+ */
+ if (operation == CMD_UPDATE)
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ resultRelInfo->ri_projectNew =
+ ExecBuildProjectionInfo(resultTargetList,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps,
+ relDesc);
+ }
+
+ /*
+ * For UPDATE/DELETE, find the appropriate junk attr now.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "wholerow");
+ /* HACK: we require it to be present for updates. */
+ if (mtstate->operation == CMD_UPDATE &&
+ !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ else
+ {
+ resultRelInfo->ri_junkAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
resultRelInfo++;
- i++;
}
- estate->es_result_relation_info = saved_resultRelInfo;
-
/* Get the target relation */
rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc;
@@ -2520,82 +2794,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
}
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
/* Set the list of arbiter indexes if needed for ON CONFLICT */
resultRelInfo = mtstate->resultRelInfo;
if (node->onConflictAction != ONCONFLICT_NONE)
@@ -2611,8 +2809,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
TupleDesc relationDesc;
TupleDesc tupDesc;
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
+ /* 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)
@@ -2668,154 +2866,29 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
PlanRowMark *rc = lfirst_node(PlanRowMark, l);
ExecRowMark *erm;
+ ExecAuxRowMark *aerm;
/* ignore "parent" rowmarks; they are irrelevant at runtime */
if (rc->isParent)
continue;
- /* find ExecRowMark (same for all subplans) */
+ /* Find ExecRowMark and build ExecAuxRowMark */
erm = ExecFindRowMark(estate, rc->rti, false);
-
- /* build ExecAuxRowMark for each subplan */
- for (i = 0; i < nplans; i++)
- {
- ExecAuxRowMark *aerm;
-
- subplan = mtstate->mt_plans[i]->plan;
- aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
- mtstate->mt_arowmarks[i] = lappend(mtstate->mt_arowmarks[i], aerm);
- }
+ aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
+ mtstate->mt_arowmarks[0] = lappend(mtstate->mt_arowmarks[0], aerm);
}
/* select first subplan */
mtstate->mt_whichplan = 0;
- subplan = (Plan *) linitial(node->plans);
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
+ * Initialize a hash table to look up UPDATE/DELETE result relations by
+ * OID if there is more than one.
*/
- for (i = 0; i < nplans; i++)
- {
- List *resultTargetList = NIL;
- bool need_projection = false;
-
- resultRelInfo = &mtstate->resultRelInfo[i];
- subplan = mtstate->mt_plans[i]->plan;
-
- /*
- * Prepare to generate tuples suitable for the target relation.
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- if (operation == CMD_INSERT)
- {
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (!tle->resjunk)
- resultTargetList = lappend(resultTargetList, tle);
- else
- need_projection = true;
- }
- }
- else
- {
- resultTargetList = (List *) list_nth(node->updateTargetLists,
- i);
- need_projection = true;
- }
-
- /*
- * The clean list must produce a tuple suitable for the result
- * relation.
- */
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- resultTargetList);
- }
-
- if (need_projection)
- {
- TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
-
- /*
- * For UPDATE, we use the old tuple to fill up missing values in
- * the tuple produced by the plan to get the new tuple.
- */
- if (operation == CMD_UPDATE)
- resultRelInfo->ri_oldTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
- resultRelInfo->ri_newTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* need an expression context to do the projection */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- resultRelInfo->ri_projectNew =
- ExecBuildProjectionInfo(resultTargetList,
- mtstate->ps.ps_ExprContext,
- resultRelInfo->ri_newTupleSlot,
- &mtstate->ps,
- relDesc);
- }
-
- /*
- * For UPDATE/DELETE, find the appropriate junk attr now.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
- if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist,
- "wholerow");
- /* HACK: we require it to be present for updates. */
- if (mtstate->operation == CMD_UPDATE &&
- !AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk wholerow column");
- }
- else
- {
- resultRelInfo->ri_junkAttno =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
- if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
- }
+ if (mtstate->operation != CMD_INSERT && mtstate->mt_nrels > 1)
+ ExecHashSubPlanResultRelsByOid(mtstate);
/*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
@@ -2849,7 +2922,7 @@ ExecEndModifyTable(ModifyTableState *node)
/*
* Allow any FDWs to shut down
*/
- for (i = 0; i < node->mt_nplans; i++)
+ for (i = 0; i < node->mt_nrels; i++)
{
ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index cd8dfcf..d168ecc 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2238,6 +2238,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_NODE_FIELD(join_info_list);
WRITE_NODE_FIELD(append_rel_list);
WRITE_NODE_FIELD(result_rel_list);
+ WRITE_NODE_FIELD(specialJunkVars);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list);
WRITE_NODE_FIELD(fkey_list);
@@ -2560,6 +2561,20 @@ _outResultRelPlanInfo(StringInfo str, const ResultRelPlanInfo *node)
}
static void
+_outSpecialJunkVarInfo(StringInfo str, const SpecialJunkVarInfo *node)
+{
+ WRITE_NODE_TYPE("SPECIALJUNKVARINFO");
+
+ WRITE_STRING_FIELD(attrname);
+ WRITE_OID_FIELD(vartype);
+ WRITE_INT_FIELD(vartypmod);
+ WRITE_OID_FIELD(varcollid);
+ WRITE_INT_FIELD(varattno);
+ WRITE_INT_FIELD(special_attno);
+ /* child_relids not printed. */
+}
+
+static void
_outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node)
{
WRITE_NODE_TYPE("PLACEHOLDERINFO");
@@ -4165,6 +4180,9 @@ outNode(StringInfo str, const void *obj)
case T_ResultRelPlanInfo:
_outResultRelPlanInfo(str, obj);
break;
+ case T_SpecialJunkVarInfo:
+ _outSpecialJunkVarInfo(str, obj);
+ break;
case T_PlaceHolderInfo:
_outPlaceHolderInfo(str, obj);
break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index d984da2..1415268 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -946,6 +946,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
double *parent_attrsizes;
int nattrs;
ListCell *l;
+ ResultRelPlanInfo *resultInfo = root->result_rel_array ?
+ root->result_rel_array[rti] : NULL;
/* Guard against stack overflow due to overly deep inheritance tree. */
check_stack_depth();
@@ -1053,10 +1055,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
adjust_appendrel_attrs(root,
(Node *) rel->joininfo,
1, &appinfo);
- childrel->reltarget->exprs = (List *)
- adjust_appendrel_attrs(root,
- (Node *) rel->reltarget->exprs,
- 1, &appinfo);
+
+ /*
+ * For a result relation, use adjust_target_appendrel_attrs() to
+ * prevent the child's wholerow Vars from being converted back to the
+ * parent's reltype.
+ */
+ if (resultInfo)
+ childrel->reltarget->exprs = (List *)
+ adjust_target_appendrel_attrs(root,
+ (Node *) rel->reltarget->exprs,
+ appinfo);
+ else
+ childrel->reltarget->exprs = (List *)
+ adjust_appendrel_attrs(root,
+ (Node *) rel->reltarget->exprs,
+ 1, &appinfo);
/*
* We have to make child entries in the EquivalenceClass data
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 07fc199..da4a804 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -2265,7 +2265,6 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
* create_modifytable_plan). Fortunately we can't be because there would
* never be grouping in an UPDATE/DELETE; but let's Assert that.
*/
- Assert(root->inhTargetKind == INHKIND_NONE);
Assert(root->grouping_map == NULL);
root->grouping_map = grouping_map;
@@ -2427,12 +2426,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
* with InitPlan output params. (We can't just do that locally in the
* MinMaxAgg node, because path nodes above here may have Agg references
* as well.) Save the mmaggregates list to tell setrefs.c to do that.
- *
- * This doesn't work if we're in an inheritance subtree (see notes in
- * create_modifytable_plan). Fortunately we can't be because there would
- * never be aggregates in an UPDATE/DELETE; but let's Assert that.
*/
- Assert(root->inhTargetKind == INHKIND_NONE);
Assert(root->minmax_aggs == NIL);
root->minmax_aggs = best_path->mmaggregates;
@@ -2662,6 +2656,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
Plan *subplan;
/*
+ * FIXME: there are no longer per-child paths.
+ *
* In an inherited UPDATE/DELETE, reference the per-child modified
* subroot while creating Plans from Paths for the child rel. This is
* a kluge, but otherwise it's too hard to ensure that Plan creation
@@ -2674,8 +2670,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
*/
subplan = create_plan_recurse(subroot, subpath, CP_EXACT_TLIST);
- /* Transfer resname/resjunk labeling, too, to keep executor happy */
- apply_tlist_labeling(subplan->targetlist, subroot->processed_tlist);
+ /* Transfer resname/resjunk labeling, too, to keep executor happy. */
+ apply_tlist_labeling(subplan->targetlist, root->processed_tlist);
subplans = lappend(subplans, subplan);
}
@@ -6807,7 +6803,6 @@ make_modifytable(PlannerInfo *root,
List *fdw_private_list;
Bitmapset *direct_modify_plans;
ListCell *lc;
- ListCell *lc2;
int i;
Assert(withCheckOptionLists == NIL ||
@@ -6872,10 +6867,9 @@ make_modifytable(PlannerInfo *root,
fdw_private_list = NIL;
direct_modify_plans = NULL;
i = 0;
- forboth(lc, resultRelations, lc2, subroots)
+ foreach(lc, resultRelations)
{
Index rti = lfirst_int(lc);
- PlannerInfo *subroot = lfirst_node(PlannerInfo, lc2);
FdwRoutine *fdwroutine;
List *fdw_private;
bool direct_modify;
@@ -6887,16 +6881,16 @@ make_modifytable(PlannerInfo *root,
* so it's not a baserel; and there are also corner cases for
* updatable views where the target rel isn't a baserel.)
*/
- if (rti < subroot->simple_rel_array_size &&
- subroot->simple_rel_array[rti] != NULL)
+ if (rti < root->simple_rel_array_size &&
+ root->simple_rel_array[rti] != NULL)
{
- RelOptInfo *resultRel = subroot->simple_rel_array[rti];
+ RelOptInfo *resultRel = root->simple_rel_array[rti];
fdwroutine = resultRel->fdwroutine;
}
else
{
- RangeTblEntry *rte = planner_rt_fetch(rti, subroot);
+ RangeTblEntry *rte = planner_rt_fetch(rti, root);
Assert(rte->rtekind == RTE_RELATION);
if (rte->relkind == RELKIND_FOREIGN_TABLE)
@@ -6919,16 +6913,17 @@ make_modifytable(PlannerInfo *root,
fdwroutine->IterateDirectModify != NULL &&
fdwroutine->EndDirectModify != NULL &&
withCheckOptionLists == NIL &&
- !has_row_triggers(subroot, rti, operation) &&
- !has_stored_generated_columns(subroot, rti))
- direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
+ !has_row_triggers(root, rti, operation) &&
+ !has_stored_generated_columns(root, rti) &&
+ i == 0) /* XXX - child tables not allowed too! */
+ direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
if (direct_modify)
direct_modify_plans = bms_add_member(direct_modify_plans, i);
if (!direct_modify &&
fdwroutine != NULL &&
fdwroutine->PlanForeignModify != NULL)
- fdw_private = fdwroutine->PlanForeignModify(subroot, node, rti, i);
+ fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
else
fdw_private = NIL;
fdw_private_list = lappend(fdw_private_list, fdw_private);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index afad8c6..4701b00 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -129,9 +129,7 @@ typedef struct
/* Local functions */
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
-static void inheritance_planner(PlannerInfo *root);
-static void grouping_planner(PlannerInfo *root, bool inheritance_update,
- double tuple_fraction);
+static void grouping_planner(PlannerInfo *root, double tuple_fraction);
static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root);
static List *remap_to_groupclause_idx(List *groupClause, List *gsets,
int *tleref_to_colnum_map);
@@ -628,7 +626,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->grouping_map = NULL;
root->minmax_aggs = NIL;
root->qual_security_level = 0;
- root->inhTargetKind = INHKIND_NONE;
root->hasRecursion = hasRecursion;
if (hasRecursion)
root->wt_param_id = assign_special_exec_param(root);
@@ -636,6 +633,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->wt_param_id = -1;
root->non_recursive_path = NULL;
root->partColsUpdated = false;
+ root->specialJunkVars = NIL;
root->result_rel_list = NIL;
/*
@@ -1005,15 +1003,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (hasResultRTEs)
remove_useless_result_rtes(root);
- /*
- * Do the main planning. If we have an inherited target relation, that
- * needs special processing, else go straight to grouping_planner.
- */
- if (parse->resultRelation &&
- rt_fetch(parse->resultRelation, parse->rtable)->inh)
- inheritance_planner(root);
- else
- grouping_planner(root, false, tuple_fraction);
+ /* Do the main planning. */
+ grouping_planner(root, tuple_fraction);
/*
* Capture the set of outer-level param IDs we have access to, for use in
@@ -1187,660 +1178,6 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr)
return (Expr *) preprocess_expression(root, (Node *) expr, EXPRKIND_PHV);
}
-/*
- * inheritance_planner
- * Generate Paths in the case where the result relation is an
- * inheritance set.
- *
- * We have to handle this case differently from cases where a source relation
- * is an inheritance set. Source inheritance is expanded at the bottom of the
- * plan tree (see allpaths.c), but target inheritance has to be expanded at
- * the top. The reason is that for UPDATE, each target relation needs a
- * different targetlist matching its own column set. Fortunately,
- * the UPDATE/DELETE target can never be the nullable side of an outer join,
- * so it's OK to generate the plan this way.
- *
- * Returns nothing; the useful output is in the Paths we attach to
- * the (UPPERREL_FINAL, NULL) upperrel stored in *root.
- *
- * Note that we have not done set_cheapest() on the final rel; it's convenient
- * to leave this to the caller.
- */
-static void
-inheritance_planner(PlannerInfo *root)
-{
- Query *parse = root->parse;
- int top_parentRTindex = parse->resultRelation;
- List *select_rtable;
- List *select_appinfos;
- List *child_appinfos;
- List *old_child_rtis;
- List *new_child_rtis;
- Bitmapset *subqueryRTindexes;
- Index next_subquery_rti;
- int nominalRelation = -1;
- Index rootRelation = 0;
- List *final_rtable = NIL;
- List *final_rowmarks = NIL;
- List *final_appendrels = NIL;
- int save_rel_array_size = 0;
- RelOptInfo **save_rel_array = NULL;
- AppendRelInfo **save_append_rel_array = NULL;
- List *subpaths = NIL;
- List *subroots = NIL;
- List *resultRelations = NIL;
- List *withCheckOptionLists = NIL;
- List *returningLists = NIL;
- List *rowMarks;
- List *updateTargetLists = NIL;
- RelOptInfo *final_rel;
- ListCell *lc;
- ListCell *lc2;
- Index rti;
- RangeTblEntry *parent_rte;
- Bitmapset *parent_relids;
- Query **parent_parses;
-
- /* Should only get here for UPDATE or DELETE */
- Assert(parse->commandType == CMD_UPDATE ||
- parse->commandType == CMD_DELETE);
-
- /*
- * We generate a modified instance of the original Query for each target
- * relation, plan that, and put all the plans into a list that will be
- * controlled by a single ModifyTable node. All the instances share the
- * same rangetable, but each instance must have its own set of subquery
- * RTEs within the finished rangetable because (1) they are likely to get
- * scribbled on during planning, and (2) it's not inconceivable that
- * subqueries could get planned differently in different cases. We need
- * not create duplicate copies of other RTE kinds, in particular not the
- * target relations, because they don't have either of those issues. Not
- * having to duplicate the target relations is important because doing so
- * (1) would result in a rangetable of length O(N^2) for N targets, with
- * at least O(N^3) work expended here; and (2) would greatly complicate
- * management of the rowMarks list.
- *
- * To begin with, generate a bitmapset of the relids of the subquery RTEs.
- */
- subqueryRTindexes = NULL;
- rti = 1;
- foreach(lc, parse->rtable)
- {
- RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
- if (rte->rtekind == RTE_SUBQUERY)
- subqueryRTindexes = bms_add_member(subqueryRTindexes, rti);
- rti++;
- }
-
- /*
- * If the parent RTE is a partitioned table, we should use that as the
- * nominal target relation, because the RTEs added for partitioned tables
- * (including the root parent) as child members of the inheritance set do
- * not appear anywhere else in the plan, so the confusion explained below
- * for non-partitioning inheritance cases is not possible.
- */
- parent_rte = rt_fetch(top_parentRTindex, parse->rtable);
- Assert(parent_rte->inh);
- if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
- {
- nominalRelation = top_parentRTindex;
- rootRelation = top_parentRTindex;
- }
-
- /*
- * Before generating the real per-child-relation plans, do a cycle of
- * planning as though the query were a SELECT. The objective here is to
- * find out which child relations need to be processed, using the same
- * expansion and pruning logic as for a SELECT. We'll then pull out the
- * RangeTblEntry-s generated for the child rels, and make use of the
- * AppendRelInfo entries for them to guide the real planning. (This is
- * rather inefficient; we could perhaps stop short of making a full Path
- * tree. But this whole function is inefficient and slated for
- * destruction, so let's not contort query_planner for that.)
- */
- {
- PlannerInfo *subroot;
-
- /*
- * Flat-copy the PlannerInfo to prevent modification of the original.
- */
- subroot = makeNode(PlannerInfo);
- memcpy(subroot, root, sizeof(PlannerInfo));
-
- /*
- * Make a deep copy of the parsetree for this planning cycle to mess
- * around with, and change it to look like a SELECT. (Hack alert: the
- * target RTE still has updatedCols set if this is an UPDATE, so that
- * expand_partitioned_rtentry will correctly update
- * subroot->partColsUpdated.)
- */
- subroot->parse = copyObject(root->parse);
-
- subroot->parse->commandType = CMD_SELECT;
- subroot->parse->resultRelation = 0;
-
- /*
- * Ensure the subroot has its own copy of the original
- * append_rel_list, since it'll be scribbled on. (Note that at this
- * point, the list only contains AppendRelInfos for flattened UNION
- * ALL subqueries.)
- */
- subroot->append_rel_list = copyObject(root->append_rel_list);
-
- /*
- * Better make a private copy of the rowMarks, too.
- */
- subroot->rowMarks = copyObject(root->rowMarks);
-
- /* There shouldn't be any OJ info to translate, as yet */
- Assert(subroot->join_info_list == NIL);
- /* and we haven't created PlaceHolderInfos, either */
- Assert(subroot->placeholder_list == NIL);
-
- /* Generate Path(s) for accessing this result relation */
- grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
-
- /* Extract the info we need. */
- select_rtable = subroot->parse->rtable;
- select_appinfos = subroot->append_rel_list;
-
- /*
- * We need to propagate partColsUpdated back, too. (The later
- * planning cycles will not set this because they won't run
- * expand_partitioned_rtentry for the UPDATE target.)
- */
- root->partColsUpdated = subroot->partColsUpdated;
- }
-
- /*----------
- * Since only one rangetable can exist in the final plan, we need to make
- * sure that it contains all the RTEs needed for any child plan. This is
- * complicated by the need to use separate subquery RTEs for each child.
- * We arrange the final rtable as follows:
- * 1. All original rtable entries (with their original RT indexes).
- * 2. All the relation RTEs generated for children of the target table.
- * 3. Subquery RTEs for children after the first. We need N * (K - 1)
- * RT slots for this, if there are N subqueries and K child tables.
- * 4. Additional RTEs generated during the child planning runs, such as
- * children of inheritable RTEs other than the target table.
- * We assume that each child planning run will create an identical set
- * of type-4 RTEs.
- *
- * So the next thing to do is append the type-2 RTEs (the target table's
- * children) to the original rtable. We look through select_appinfos
- * to find them.
- *
- * To identify which AppendRelInfos are relevant as we thumb through
- * select_appinfos, we need to look for both direct and indirect children
- * of top_parentRTindex, so we use a bitmap of known parent relids.
- * expand_inherited_rtentry() always processes a parent before any of that
- * parent's children, so we should see an intermediate parent before its
- * children.
- *----------
- */
- child_appinfos = NIL;
- old_child_rtis = NIL;
- new_child_rtis = NIL;
- parent_relids = bms_make_singleton(top_parentRTindex);
- foreach(lc, select_appinfos)
- {
- AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
- RangeTblEntry *child_rte;
-
- /* append_rel_list contains all append rels; ignore others */
- if (!bms_is_member(appinfo->parent_relid, parent_relids))
- continue;
-
- /* remember relevant AppendRelInfos for use below */
- child_appinfos = lappend(child_appinfos, appinfo);
-
- /* extract RTE for this child rel */
- child_rte = rt_fetch(appinfo->child_relid, select_rtable);
-
- /* and append it to the original rtable */
- parse->rtable = lappend(parse->rtable, child_rte);
-
- /* remember child's index in the SELECT rtable */
- old_child_rtis = lappend_int(old_child_rtis, appinfo->child_relid);
-
- /* and its new index in the final rtable */
- new_child_rtis = lappend_int(new_child_rtis, list_length(parse->rtable));
-
- /* if child is itself partitioned, update parent_relids */
- if (child_rte->inh)
- {
- Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
- parent_relids = bms_add_member(parent_relids, appinfo->child_relid);
- }
- }
-
- /*
- * It's possible that the RTIs we just assigned for the child rels in the
- * final rtable are different from what they were in the SELECT query.
- * Adjust the AppendRelInfos so that they will correctly map RT indexes to
- * the final indexes. We can do this left-to-right since no child rel's
- * final RT index could be greater than what it had in the SELECT query.
- */
- forboth(lc, old_child_rtis, lc2, new_child_rtis)
- {
- int old_child_rti = lfirst_int(lc);
- int new_child_rti = lfirst_int(lc2);
-
- if (old_child_rti == new_child_rti)
- continue; /* nothing to do */
-
- Assert(old_child_rti > new_child_rti);
-
- ChangeVarNodes((Node *) child_appinfos,
- old_child_rti, new_child_rti, 0);
- }
-
- /*
- * Now set up rangetable entries for subqueries for additional children
- * (the first child will just use the original ones). These all have to
- * look more or less real, or EXPLAIN will get unhappy; so we just make
- * them all clones of the original subqueries.
- */
- next_subquery_rti = list_length(parse->rtable) + 1;
- if (subqueryRTindexes != NULL)
- {
- int n_children = list_length(child_appinfos);
-
- while (n_children-- > 1)
- {
- int oldrti = -1;
-
- while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
- {
- RangeTblEntry *subqrte;
-
- subqrte = rt_fetch(oldrti, parse->rtable);
- parse->rtable = lappend(parse->rtable, copyObject(subqrte));
- }
- }
- }
-
- /*
- * The query for each child is obtained by translating the query for its
- * immediate parent, since the AppendRelInfo data we have shows deltas
- * between parents and children. We use the parent_parses array to
- * remember the appropriate query trees. This is indexed by parent relid.
- * Since the maximum number of parents is limited by the number of RTEs in
- * the SELECT query, we use that number to allocate the array. An extra
- * entry is needed since relids start from 1.
- */
- parent_parses = (Query **) palloc0((list_length(select_rtable) + 1) *
- sizeof(Query *));
- parent_parses[top_parentRTindex] = parse;
-
- /*
- * And now we can get on with generating a plan for each child table.
- */
- foreach(lc, child_appinfos)
- {
- AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
- Index this_subquery_rti = next_subquery_rti;
- Query *parent_parse;
- PlannerInfo *subroot;
- RangeTblEntry *child_rte;
- RelOptInfo *sub_final_rel;
- Path *subpath;
- ResultRelPlanInfo *resultInfo;
- AttrNumber resno;
-
- /*
- * expand_inherited_rtentry() always processes a parent before any of
- * that parent's children, so the parent query for this relation
- * should already be available.
- */
- parent_parse = parent_parses[appinfo->parent_relid];
- Assert(parent_parse != NULL);
-
- /*
- * We need a working copy of the PlannerInfo so that we can control
- * propagation of information back to the main copy.
- */
- subroot = makeNode(PlannerInfo);
- memcpy(subroot, root, sizeof(PlannerInfo));
-
- /*
- * Generate modified query with this rel as target. We first apply
- * adjust_appendrel_attrs, which copies the Query and changes
- * references to the parent RTE to refer to the current child RTE,
- * then fool around with subquery RTEs.
- */
- subroot->parse = (Query *)
- adjust_appendrel_attrs(subroot,
- (Node *) parent_parse,
- 1, &appinfo);
-
- /*
- * If there are securityQuals attached to the parent, move them to the
- * child rel (they've already been transformed properly for that).
- */
- parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable);
- child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable);
- child_rte->securityQuals = parent_rte->securityQuals;
- parent_rte->securityQuals = NIL;
-
- /*
- * HACK: setting this to a value other than INHKIND_NONE signals to
- * relation_excluded_by_constraints() to treat the result relation as
- * being an appendrel member.
- */
- subroot->inhTargetKind =
- (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED;
-
- /*
- * If this child is further partitioned, remember it as a parent.
- * Since a partitioned table does not have any data, we don't need to
- * create a plan for it, and we can stop processing it here. We do,
- * however, need to remember its modified PlannerInfo for use when
- * processing its children, since we'll update their varnos based on
- * the delta from immediate parent to child, not from top to child.
- *
- * Note: a very non-obvious point is that we have not yet added
- * duplicate subquery RTEs to the subroot's rtable. We mustn't,
- * because then its children would have two sets of duplicates,
- * confusing matters.
- */
- if (child_rte->inh)
- {
- Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
- parent_parses[appinfo->child_relid] = subroot->parse;
- continue;
- }
-
- /*
- * Set the nominal target relation of the ModifyTable node if not
- * already done. If the target is a partitioned table, we already set
- * nominalRelation to refer to the partition root, above. For
- * non-partitioned inheritance cases, we'll use the first child
- * relation (even if it's excluded) as the nominal target relation.
- * Because of the way expand_inherited_rtentry works, that should be
- * the RTE representing the parent table in its role as a simple
- * member of the inheritance set.
- *
- * It would be logically cleaner to *always* use the inheritance
- * parent RTE as the nominal relation; but that RTE is not otherwise
- * referenced in the plan in the non-partitioned inheritance case.
- * Instead the duplicate child RTE created by expand_inherited_rtentry
- * is used elsewhere in the plan, so using the original parent RTE
- * would give rise to confusing use of multiple aliases in EXPLAIN
- * output for what the user will think is the "same" table. OTOH,
- * it's not a problem in the partitioned inheritance case, because
- * there is no duplicate RTE for the parent.
- */
- if (nominalRelation < 0)
- nominalRelation = appinfo->child_relid;
-
- /*
- * As above, each child plan run needs its own append_rel_list and
- * rowmarks, which should start out as pristine copies of the
- * originals. There can't be any references to UPDATE/DELETE target
- * rels in them; but there could be subquery references, which we'll
- * fix up in a moment.
- */
- subroot->append_rel_list = copyObject(root->append_rel_list);
- subroot->rowMarks = copyObject(root->rowMarks);
-
- /* Child result relation's only ResultRelPlanInfo goes here. */
- subroot->result_rel_list = NIL;
-
- /*
- * If this isn't the first child Query, adjust Vars and jointree
- * entries to reference the appropriate set of subquery RTEs.
- */
- if (final_rtable != NIL && subqueryRTindexes != NULL)
- {
- int oldrti = -1;
-
- while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
- {
- Index newrti = next_subquery_rti++;
-
- ChangeVarNodes((Node *) subroot->parse, oldrti, newrti, 0);
- ChangeVarNodes((Node *) subroot->append_rel_list,
- oldrti, newrti, 0);
- ChangeVarNodes((Node *) subroot->rowMarks, oldrti, newrti, 0);
- }
- }
-
- /* There shouldn't be any OJ info to translate, as yet */
- Assert(subroot->join_info_list == NIL);
- /* and we haven't created PlaceHolderInfos, either */
- Assert(subroot->placeholder_list == NIL);
-
- /* Generate Path(s) for accessing this result relation */
- grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
-
- /*
- * Select cheapest path in case there's more than one. We always run
- * modification queries to conclusion, so we care only for the
- * cheapest-total path.
- */
- sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
- set_cheapest(sub_final_rel);
- subpath = sub_final_rel->cheapest_total_path;
-
- /*
- * If this child rel was excluded by constraint exclusion, exclude it
- * from the result plan.
- */
- if (IS_DUMMY_REL(sub_final_rel))
- continue;
-
- /*
- * If this is the first non-excluded child, its post-planning rtable
- * becomes the initial contents of final_rtable; otherwise, copy its
- * modified subquery RTEs into final_rtable, to ensure we have sane
- * copies of those. Also save the first non-excluded child's version
- * of the rowmarks list; we assume all children will end up with
- * equivalent versions of that. Likewise for append_rel_list.
- */
- if (final_rtable == NIL)
- {
- final_rtable = subroot->parse->rtable;
- final_rowmarks = subroot->rowMarks;
- final_appendrels = subroot->append_rel_list;
- }
- else
- {
- Assert(list_length(final_rtable) ==
- list_length(subroot->parse->rtable));
- if (subqueryRTindexes != NULL)
- {
- int oldrti = -1;
-
- while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
- {
- Index newrti = this_subquery_rti++;
- RangeTblEntry *subqrte;
- ListCell *newrticell;
-
- subqrte = rt_fetch(newrti, subroot->parse->rtable);
- newrticell = list_nth_cell(final_rtable, newrti - 1);
- lfirst(newrticell) = subqrte;
- }
- }
- }
-
- /*
- * We need to collect all the RelOptInfos from all child plans into
- * the main PlannerInfo, since setrefs.c will need them. We use the
- * last child's simple_rel_array, so we have to propagate forward the
- * RelOptInfos that were already built in previous children.
- */
- Assert(subroot->simple_rel_array_size >= save_rel_array_size);
- for (rti = 1; rti < save_rel_array_size; rti++)
- {
- RelOptInfo *brel = save_rel_array[rti];
-
- if (brel)
- subroot->simple_rel_array[rti] = brel;
- }
- save_rel_array_size = subroot->simple_rel_array_size;
- save_rel_array = subroot->simple_rel_array;
- save_append_rel_array = subroot->append_rel_array;
-
- /*
- * Make sure any initplans from this rel get into the outer list. Note
- * we're effectively assuming all children generate the same
- * init_plans.
- */
- root->init_plans = subroot->init_plans;
-
- /* Build list of sub-paths */
- subpaths = lappend(subpaths, subpath);
-
- /* Build list of modified subroots, too */
- subroots = lappend(subroots, subroot);
-
- /* Build list of target-relation RT indexes */
- resultRelations = lappend_int(resultRelations, appinfo->child_relid);
-
- /* Build lists of per-relation WCO and RETURNING targetlists */
- if (parse->withCheckOptions)
- withCheckOptionLists = lappend(withCheckOptionLists,
- subroot->parse->withCheckOptions);
- if (parse->returningList)
- returningLists = lappend(returningLists,
- subroot->parse->returningList);
-
- Assert(list_length(subroot->result_rel_list) == 1);
- resultInfo = linitial(subroot->result_rel_list);
- if (parse->commandType == CMD_UPDATE)
- updateTargetLists = lappend(updateTargetLists,
- resultInfo->updateTargetList);
- root->result_rel_list = lappend(root->result_rel_list, resultInfo);
-
- /*
- * While we are here, renumber the top-level targetlist so that
- * resnos match those in the top-level plan's targetlist.
- * XXX - really, this is to prevent apply_tlist_labeling() from
- * crashing.
- */
- resno = 1;
- foreach(lc, subroot->processed_tlist)
- {
- TargetEntry *tle = lfirst(lc);
-
- tle->resno = resno++;
- }
-
- Assert(!parse->onConflict);
- }
-
- /* Result path must go into outer query's FINAL upperrel */
- final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
-
- /*
- * We don't currently worry about setting final_rel's consider_parallel
- * flag in this case, nor about allowing FDWs or create_upper_paths_hook
- * to get control here.
- */
-
- if (subpaths == NIL)
- {
- /*
- * We managed to exclude every child rel, so generate a dummy path
- * representing the empty set. Although it's clear that no data will
- * be updated or deleted, we will still need to have a ModifyTable
- * node so that any statement triggers are executed. (This could be
- * cleaner if we fixed nodeModifyTable.c to support zero child nodes,
- * but that probably wouldn't be a net win.)
- */
- Path *dummy_path;
-
- /* tlist processing never got done, either */
- Assert(root->result_rel_list == NIL);
- root->processed_tlist = preprocess_targetlist(root);
- /* that should also have a made a ResultRelPlanInfo for us. */
- Assert(list_length(root->result_rel_list) == 1);
- final_rel->reltarget = create_pathtarget(root, root->processed_tlist);
-
- /* Make a dummy path, cf set_dummy_rel_pathlist() */
- dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL,
- NIL, NULL, 0, false,
- NIL, -1);
-
- /* These lists must be nonempty to make a valid ModifyTable node */
- subpaths = list_make1(dummy_path);
- subroots = list_make1(root);
- resultRelations = list_make1_int(parse->resultRelation);
- if (parse->withCheckOptions)
- withCheckOptionLists = list_make1(parse->withCheckOptions);
- if (parse->returningList)
- returningLists = list_make1(parse->returningList);
- /* ExecInitModifyTable insists that updateTargetList is present. */
- if (parse->commandType == CMD_UPDATE)
- {
- ResultRelPlanInfo *resultInfo;
-
- resultInfo = linitial(root->result_rel_list);
- updateTargetLists = list_make1(resultInfo->updateTargetList);
- }
- /* Disable tuple routing, too, just to be safe */
- root->partColsUpdated = false;
- }
- else
- {
- /*
- * Put back the final adjusted rtable into the master copy of the
- * Query. (We mustn't do this if we found no non-excluded children,
- * since we never saved an adjusted rtable at all.)
- */
- parse->rtable = final_rtable;
- root->simple_rel_array_size = save_rel_array_size;
- root->simple_rel_array = save_rel_array;
- root->append_rel_array = save_append_rel_array;
-
- /* Must reconstruct master's simple_rte_array, too */
- root->simple_rte_array = (RangeTblEntry **)
- palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *));
- rti = 1;
- foreach(lc, final_rtable)
- {
- RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
- root->simple_rte_array[rti++] = rte;
- }
-
- /* Put back adjusted rowmarks and appendrels, too */
- root->rowMarks = final_rowmarks;
- root->append_rel_list = final_appendrels;
- }
-
- /*
- * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
- * have dealt with fetching non-locked marked rows, else we need to have
- * ModifyTable do that.
- */
- if (parse->rowMarks)
- rowMarks = NIL;
- else
- rowMarks = root->rowMarks;
-
- /* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
- add_path(final_rel, (Path *)
- create_modifytable_path(root, final_rel,
- parse->commandType,
- parse->canSetTag,
- nominalRelation,
- rootRelation,
- root->partColsUpdated,
- resultRelations,
- subpaths,
- subroots,
- updateTargetLists,
- withCheckOptionLists,
- returningLists,
- rowMarks,
- NULL,
- assign_special_exec_param(root)));
-}
-
/*--------------------
* grouping_planner
* Perform planning steps related to grouping, aggregation, etc.
@@ -1848,11 +1185,6 @@ inheritance_planner(PlannerInfo *root)
* This function adds all required top-level processing to the scan/join
* Path(s) produced by query_planner.
*
- * If inheritance_update is true, we're being called from inheritance_planner
- * and should not include a ModifyTable step in the resulting Path(s).
- * (inheritance_planner will create a single ModifyTable node covering all the
- * target tables.)
- *
* tuple_fraction is the fraction of tuples we expect will be retrieved.
* tuple_fraction is interpreted as follows:
* 0: expect all tuples to be retrieved (normal case)
@@ -1870,8 +1202,7 @@ inheritance_planner(PlannerInfo *root)
*--------------------
*/
static void
-grouping_planner(PlannerInfo *root, bool inheritance_update,
- double tuple_fraction)
+grouping_planner(PlannerInfo *root, double tuple_fraction)
{
Query *parse = root->parse;
int64 offset_est = 0;
@@ -2363,18 +1694,83 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
offset_est, count_est);
}
- /*
- * If this is an INSERT/UPDATE/DELETE, and we're not being called from
- * inheritance_planner, add the ModifyTable node.
- */
- if (parse->commandType != CMD_SELECT && !inheritance_update)
+ /* If this is an INSERT/UPDATE/DELETE, add the ModifyTable node. */
+ if (parse->commandType != CMD_SELECT)
{
Index rootRelation;
- List *withCheckOptionLists;
- List *returningLists;
- List *rowMarks;
+ List *resultRelations = NIL;
List *updateTargetLists = NIL;
+ List *withCheckOptionLists = NIL;
+ List *returningLists = NIL;
+ List *rowMarks;
AttrNumber resno;
+ ListCell *l;
+
+ if (list_length(root->result_rel_list) > 1)
+ {
+ /* Inherited UPDATE/DELETE, can't be an INSERT. */
+ foreach(l, root->result_rel_list)
+ {
+ ResultRelPlanInfo *resultInfo = lfirst(l);
+ Index resultRelation = resultInfo->resultRelation;
+
+ /* Add only leaf children to ModifyTable. */
+ if (planner_rt_fetch(resultInfo->resultRelation,
+ root)->inh)
+ continue;
+
+ /* Also, check that the leaf rel has not turned dummy. */
+ if (IS_DUMMY_REL(find_base_rel(root, resultRelation)))
+ continue;
+
+ resultRelations = lappend_int(resultRelations,
+ resultInfo->resultRelation);
+ if (resultInfo->updateTargetList)
+ updateTargetLists = lappend(updateTargetLists,
+ resultInfo->updateTargetList);
+ if (resultInfo->withCheckOptions)
+ withCheckOptionLists = lappend(withCheckOptionLists,
+ resultInfo->withCheckOptions);
+ if (resultInfo->returningList)
+ returningLists = lappend(returningLists,
+ resultInfo->returningList);
+ }
+
+ /* Add the root relation itself if all leaves were dummy. */
+ if (resultRelations == NIL)
+ {
+ ResultRelPlanInfo *resultInfo = linitial(root->result_rel_list);
+
+ resultRelations = list_make1_int(parse->resultRelation);
+ updateTargetLists = list_make1(resultInfo->updateTargetList);
+ if (resultInfo->withCheckOptions)
+ withCheckOptionLists = list_make1(resultInfo->withCheckOptions);
+ if (resultInfo->returningList)
+ returningLists = list_make1(resultInfo->returningList);
+ }
+ }
+ else if (list_length(root->result_rel_list) == 1)
+ {
+ /* Non-inherited UPDATE/DELETE. */
+ ResultRelPlanInfo *resultInfo = linitial(root->result_rel_list);
+
+ resultRelations = list_make1_int(resultInfo->resultRelation);
+ if (resultInfo->updateTargetList)
+ updateTargetLists = list_make1(resultInfo->updateTargetList);
+ if (resultInfo->withCheckOptions)
+ withCheckOptionLists = list_make1(resultInfo->withCheckOptions);
+ if (resultInfo->returningList)
+ returningLists = list_make1(resultInfo->returningList);
+ }
+ else
+ {
+ /* INSERT. */
+ resultRelations = list_make1_int(parse->resultRelation);
+ if (parse->withCheckOptions)
+ withCheckOptionLists = list_make1(parse->withCheckOptions);
+ if (parse->returningList)
+ returningLists = list_make1(parse->returningList);
+ }
/*
* If target is a partition root table, we need to mark the
@@ -2387,20 +1783,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
rootRelation = 0;
/*
- * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
- * needed.
- */
- if (parse->withCheckOptions)
- withCheckOptionLists = list_make1(parse->withCheckOptions);
- else
- withCheckOptionLists = NIL;
-
- if (parse->returningList)
- returningLists = list_make1(parse->returningList);
- else
- returningLists = NIL;
-
- /*
* If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node
* will have dealt with fetching non-locked marked rows, else we
* need to have ModifyTable do that.
@@ -2410,15 +1792,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
else
rowMarks = root->rowMarks;
- if (root->result_rel_list)
- {
- ResultRelPlanInfo *resultInfo =
- linitial(root->result_rel_list);
-
- if (resultInfo->updateTargetList)
- updateTargetLists = list_make1(resultInfo->updateTargetList);
- }
-
/*
* While we are here, renumber the top-level targetlist so that
* resnos match those in the top-level plan's targetlist.
@@ -2439,8 +1812,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
parse->canSetTag,
parse->resultRelation,
rootRelation,
- false,
- list_make1_int(parse->resultRelation),
+ root->partColsUpdated,
+ resultRelations,
list_make1(path),
list_make1(root),
updateTargetLists,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 2c31a7c..6a52c6c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -811,6 +811,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_ModifyTable:
{
ModifyTable *splan = (ModifyTable *) plan;
+ Plan *subplan = linitial(splan->plans);
Assert(splan->plan.targetlist == NIL);
Assert(splan->plan.qual == NIL);
@@ -825,22 +826,17 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
{
List *newRL = NIL;
ListCell *lcrl,
- *lcrr,
- *lcp;
+ *lcrr;
/*
- * Pass each per-subplan returningList through
+ * Pass each per-resultrel returningList through
* set_returning_clause_references().
*/
Assert(list_length(splan->returningLists) == list_length(splan->resultRelations));
- Assert(list_length(splan->returningLists) == list_length(splan->plans));
- forthree(lcrl, splan->returningLists,
- lcrr, splan->resultRelations,
- lcp, splan->plans)
+ forboth(lcrl, splan->returningLists, lcrr, splan->resultRelations)
{
List *rlist = (List *) lfirst(lcrl);
Index resultrel = lfirst_int(lcrr);
- Plan *subplan = (Plan *) lfirst(lcp);
rlist = set_returning_clause_references(root,
rlist,
@@ -2710,16 +2706,13 @@ set_update_tlist_references(PlannerInfo *root,
int rtoffset)
{
ListCell *lc1,
- *lc2,
- *lc3;
+ *lc2;
- forthree(lc1, splan->resultRelations,
- lc2, splan->updateTargetLists,
- lc3, root->result_rel_list)
+ forboth(lc1, splan->resultRelations, lc2, splan->updateTargetLists)
{
Index resultRel = lfirst_int(lc1);
List *updateTargetList = lfirst(lc2);
- ResultRelPlanInfo *resultInfo = lfirst(lc3);
+ ResultRelPlanInfo *resultInfo = root->result_rel_array[resultRel];
AttrNumber resno;
ListCell *lc;
indexed_tlist *itlist;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 1452172..d24ac34 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -926,7 +926,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->grouping_map = NULL;
subroot->minmax_aggs = NIL;
subroot->qual_security_level = 0;
- subroot->inhTargetKind = INHKIND_NONE;
subroot->hasRecursion = false;
subroot->wt_param_id = -1;
subroot->non_recursive_path = NULL;
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 89f7d89..b92e3ed 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -123,9 +123,14 @@ preprocess_targetlist(PlannerInfo *root)
tlist = expand_targetlist(tlist, command_type,
result_relation, target_relation);
- /* Make ResultRelPlanInfo for a UPDATE/DELETE result relation. */
+ /*
+ * Make ResultRelPlanInfo for a UPDATE/DELETE result relation. If the
+ * relation has inheritance children, they will get one too when
+ * query_planner() adds them.
+ */
if (target_relation && command_type != CMD_INSERT)
- make_result_relation_info(root, result_relation, target_relation);
+ make_result_relation_info(root, result_relation, target_relation,
+ NULL);
/*
* Add necessary junk columns for rowmarked rels. These values are needed
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063..f020250 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -18,6 +18,7 @@
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
+#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
@@ -29,6 +30,7 @@ typedef struct
PlannerInfo *root;
int nappinfos;
AppendRelInfo **appinfos;
+ bool need_parent_wholerow;
} adjust_appendrel_attrs_context;
static void make_inh_translation_list(Relation oldrelation,
@@ -37,8 +39,6 @@ static void make_inh_translation_list(Relation oldrelation,
AppendRelInfo *appinfo);
static Node *adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context);
-static List *adjust_inherited_tlist(List *tlist,
- AppendRelInfo *context);
/*
@@ -200,42 +200,42 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
context.root = root;
context.nappinfos = nappinfos;
context.appinfos = appinfos;
+ context.need_parent_wholerow = true;
/* If there's nothing to adjust, don't call this function. */
Assert(nappinfos >= 1 && appinfos != NULL);
- /*
- * Must be prepared to start with a Query or a bare expression tree.
- */
- if (node && IsA(node, Query))
- {
- Query *newnode;
- int cnt;
+ /* Should never be translating a Query tree. */
+ Assert (node == NULL || !IsA(node, Query));
+ result = adjust_appendrel_attrs_mutator(node, &context);
- newnode = query_tree_mutator((Query *) node,
- adjust_appendrel_attrs_mutator,
- (void *) &context,
- QTW_IGNORE_RC_SUBQUERIES);
- for (cnt = 0; cnt < nappinfos; cnt++)
- {
- AppendRelInfo *appinfo = appinfos[cnt];
+ return result;
+}
- if (newnode->resultRelation == appinfo->parent_relid)
- {
- newnode->resultRelation = appinfo->child_relid;
- /* Fix tlist resnos too, if it's inherited UPDATE */
- if (newnode->commandType == CMD_UPDATE)
- newnode->targetList =
- adjust_inherited_tlist(newnode->targetList,
- appinfo);
- break;
- }
- }
+/*
+ * adjust_target_appendrel_attrs
+ * like adjust_appendrel_attrs, but treats wholerow Vars a bit
+ * differently in that it doesn't convert any child table
+ * wholerows contained in 'node' back to the parent reltype.
+ */
+Node *
+adjust_target_appendrel_attrs(PlannerInfo *root, Node *node,
+ AppendRelInfo *appinfo)
+{
+ Node *result;
+ adjust_appendrel_attrs_context context;
- result = (Node *) newnode;
- }
- else
- result = adjust_appendrel_attrs_mutator(node, &context);
+ context.root = root;
+ context.nappinfos = 1;
+ context.appinfos = &appinfo;
+ context.need_parent_wholerow = false;
+
+ /* If there's nothing to adjust, don't call this function. */
+ Assert(appinfo != NULL);
+
+ /* Should never be translating a Query tree. */
+ Assert (node == NULL || !IsA(node, Query));
+ result = adjust_appendrel_attrs_mutator(node, &context);
return result;
}
@@ -277,9 +277,25 @@ adjust_appendrel_attrs_mutator(Node *node,
{
Node *newnode;
+ /*
+ * If this Var appears to have a unusual attno assigned,
+ * it must be one of the "special" Vars assigned to parent
+ * target table.
+ */
if (var->varattno > list_length(appinfo->translated_vars))
- elog(ERROR, "attribute %d of relation \"%s\" does not exist",
- var->varattno, get_rel_name(appinfo->parent_reloid));
+ {
+ SpecialJunkVarInfo *sjv =
+ get_special_junk_var(context->root, var->varattno);
+
+ if (sjv == NULL)
+ elog(ERROR, "attribute %d of relation \"%s\" does not exist",
+ var->varattno, get_rel_name(appinfo->parent_reloid));
+ if (!bms_is_member(appinfo->child_relid, sjv->child_relids))
+ return (Node *) makeNullConst(var->vartype,
+ var->vartypmod,
+ var->varcollid);
+ var->varattno = sjv->varattno;
+ }
newnode = copyObject(list_nth(appinfo->translated_vars,
var->varattno - 1));
if (newnode == NULL)
@@ -298,7 +314,10 @@ adjust_appendrel_attrs_mutator(Node *node,
if (OidIsValid(appinfo->child_reltype))
{
Assert(var->vartype == appinfo->parent_reltype);
- if (appinfo->parent_reltype != appinfo->child_reltype)
+ /* Make sure the Var node has the right type ID, too */
+ var->vartype = appinfo->child_reltype;
+ if (appinfo->parent_reltype != appinfo->child_reltype &&
+ context->need_parent_wholerow)
{
ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
@@ -306,8 +325,6 @@ adjust_appendrel_attrs_mutator(Node *node,
r->resulttype = appinfo->parent_reltype;
r->convertformat = COERCE_IMPLICIT_CAST;
r->location = -1;
- /* Make sure the Var node has the right type ID, too */
- var->vartype = appinfo->child_reltype;
return (Node *) r;
}
}
@@ -630,13 +647,9 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
* (We do all this work in special cases so that preptlist.c is fast for
* the typical case.)
*
- * The given tlist has already been through expression_tree_mutator;
- * therefore the TargetEntry nodes are fresh copies that it's okay to
- * scribble on.
- *
- * Note that this is not needed for INSERT because INSERT isn't inheritable.
+ * Note that the caller must be okay with the input tlist being scribbled on.
*/
-static List *
+List *
adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
{
bool changed_it = false;
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd3..a79b77d 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -19,8 +19,10 @@
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/inherit.h"
#include "optimizer/optimizer.h"
@@ -29,16 +31,19 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "partitioning/partdesc.h"
#include "partitioning/partprune.h"
+#include "utils/lsyscache.h"
#include "utils/rel.h"
static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
RangeTblEntry *parentrte,
Index parentRTindex, Relation parentrel,
- PlanRowMark *top_parentrc, LOCKMODE lockmode);
+ PlanRowMark *top_parentrc, LOCKMODE lockmode,
+ RelOptInfo *rootrelinfo);
static void expand_single_inheritance_child(PlannerInfo *root,
RangeTblEntry *parentrte,
Index parentRTindex, Relation parentrel,
@@ -49,6 +54,15 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
List *translated_vars);
static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte, Index rti);
+static void add_child_junk_vars(PlannerInfo *root, RelOptInfo *childrel,
+ Relation childrelation, RelOptInfo *rootrel);
+static bool need_special_junk_var(PlannerInfo *root,
+ TargetEntry *tle,
+ Index child_relid);
+static void add_special_junk_var(PlannerInfo *root,
+ TargetEntry *tle,
+ Index child_relid,
+ RelOptInfo *rootrel);
/*
@@ -85,6 +99,8 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
PlanRowMark *oldrc;
bool old_isParent = false;
int old_allMarkTypes = 0;
+ List *newvars = NIL;
+ ListCell *l;
Assert(rte->inh); /* else caller error */
@@ -141,7 +157,8 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
* extract the partition key columns of all the partitioned tables.
*/
expand_partitioned_rtentry(root, rel, rte, rti,
- oldrelation, oldrc, lockmode);
+ oldrelation, oldrc, lockmode,
+ rel);
}
else
{
@@ -151,7 +168,6 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
* children, so it's not possible for both cases to apply.)
*/
List *inhOIDs;
- ListCell *l;
/* Scan for all members of inheritance set, acquire needed locks */
inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
@@ -180,6 +196,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
+ RelOptInfo *childrel;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -205,7 +222,14 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
&childrte, &childRTindex);
/* Create the otherrel RelOptInfo too. */
- (void) build_simple_rel(root, childRTindex, rel);
+ childrel = build_simple_rel(root, childRTindex, rel);
+
+ if (rti == root->parse->resultRelation)
+ {
+ Assert(root->parse->commandType == CMD_UPDATE ||
+ root->parse->commandType == CMD_DELETE);
+ add_child_junk_vars(root, childrel, newrelation, rel);
+ }
/* Close child relations, but keep locks */
if (childOID != parentOID)
@@ -226,7 +250,6 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
Var *var;
TargetEntry *tle;
char resname[32];
- List *newvars = NIL;
/* The old PlanRowMark should already have necessitated adding TID */
Assert(old_allMarkTypes & ~(1 << ROW_MARK_COPY));
@@ -265,14 +288,56 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
root->processed_tlist = lappend(root->processed_tlist, tle);
newvars = lappend(newvars, var);
}
+ }
+
+ /*
+ * If parent is a result relation, add necessary junk columns to the
+ * top level targetlist and parent's reltarget.
+ */
+ if (rti == root->parse->resultRelation)
+ {
+ int resno = list_length(root->processed_tlist) + 1;
+ TargetEntry *tle;
+ Var *var;
/*
- * Add the newly added Vars to parent's reltarget. We needn't worry
- * about the children's reltargets, they'll be made later.
+ * First, we will need a "tableoid" for the executor to identify
+ * the result relation to update or delete for a given tuple.
*/
- add_vars_to_targetlist(root, newvars, bms_make_singleton(0), false);
+ var= makeVar(rti, TableOidAttributeNumber, OIDOID, -1, InvalidOid, 0);
+ tle = makeTargetEntry((Expr *) var,
+ resno,
+ pstrdup("tableoid"),
+ true);
+ ++resno;
+
+ root->processed_tlist = lappend(root->processed_tlist, tle);
+ newvars = lappend(newvars, var);
+
+ /* Now add "special" child junk Vars. */
+ foreach(l, root->specialJunkVars)
+ {
+ SpecialJunkVarInfo *sjv = lfirst(l);
+ Oid vartype = sjv->special_attno == 0 ?
+ oldrelation->rd_rel->reltype :
+ sjv->vartype;
+
+ var = makeVar(rti, sjv->special_attno, vartype,
+ sjv->vartypmod, sjv->varcollid, 0);
+ tle = makeTargetEntry((Expr *) var, resno, sjv->attrname, true);
+ root->processed_tlist = lappend(root->processed_tlist, tle);
+ newvars = lappend(newvars, var);
+ ++resno;
+ }
}
+ /*
+ * Add the newly added Vars to parent's reltarget. We needn't worry
+ * about the children's reltargets, they'll be made later.
+ */
+ if (newvars != NIL)
+ add_vars_to_targetlist(root, newvars, bms_make_singleton(0), false);
+
table_close(oldrelation, NoLock);
}
@@ -284,7 +349,8 @@ static void
expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
RangeTblEntry *parentrte,
Index parentRTindex, Relation parentrel,
- PlanRowMark *top_parentrc, LOCKMODE lockmode)
+ PlanRowMark *top_parentrc, LOCKMODE lockmode,
+ RelOptInfo *rootrelinfo)
{
PartitionDesc partdesc;
Bitmapset *live_parts;
@@ -379,11 +445,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
relinfo->all_partrels = bms_add_members(relinfo->all_partrels,
childrelinfo->relids);
+ if (rootrelinfo->relid == root->parse->resultRelation)
+ {
+ Assert(root->parse->commandType == CMD_UPDATE ||
+ root->parse->commandType == CMD_DELETE);
+ add_child_junk_vars(root, childrelinfo, childrel, rootrelinfo);
+ }
+
/* If this child is itself partitioned, recurse */
if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
expand_partitioned_rtentry(root, childrelinfo,
childrte, childRTindex,
- childrel, top_parentrc, lockmode);
+ childrel, top_parentrc, lockmode,
+ rootrelinfo);
/* Close child relation, but keep locks */
table_close(childrel, NoLock);
@@ -391,6 +465,212 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
}
/*
+ * add_child_junk_vars
+ * Check if the row-identifying junk column(s) required by this child
+ * table is present in the top-level targetlist and if not, add. The
+ * junk column's properties (name, type, or Var attribute number) may
+ * require us to add "special" junk Vars to parent's targetlist which it
+ * or any of its other children may not be able to produce themselves.
+ *
+ * Note: this only concerns foreign child tables
+ */
+static void
+add_child_junk_vars(PlannerInfo *root, RelOptInfo *childrel,
+ Relation childrelation,
+ RelOptInfo *rootrel)
+{
+ RangeTblEntry *childrte = root->simple_rte_array[childrel->relid];
+ Query parsetree;
+ List *child_junk_vars;
+ ListCell *lc;
+
+ /* Currently, only foreign children may require such junk columns. */
+ if (childrel->fdwroutine == NULL ||
+ childrel->fdwroutine->AddForeignUpdateTargets == NULL)
+ return;
+
+ /*
+ * Ask the table's FDW what junk Vars it needs.
+ */
+ memcpy(&parsetree, root->parse, sizeof(Query));
+ parsetree.resultRelation = childrel->relid;
+ parsetree.targetList = NIL;
+ childrel->fdwroutine->AddForeignUpdateTargets(&parsetree,
+ childrte,
+ childrelation);
+ child_junk_vars = parsetree.targetList;
+
+ /*
+ * We need the "old" tuple to fill up the values for unassigned-to
+ * attributes in the case of UPDATE. We will need it also if there
+ * are any DELETE row triggers. This matches what
+ * rewriteTargetListUD() does.
+ */
+ if (parsetree.commandType == CMD_UPDATE ||
+ (childrelation->trigdesc &&
+ (childrelation->trigdesc->trig_delete_after_row ||
+ childrelation->trigdesc->trig_delete_before_row)))
+ {
+ Var *var;
+ TargetEntry *tle;
+
+ var = makeWholeRowVar(childrte,
+ parsetree.resultRelation,
+ 0,
+ false);
+
+ tle = makeTargetEntry((Expr *) var,
+ list_length(child_junk_vars) + 1,
+ "wholerow",
+ true);
+ child_junk_vars = lappend(child_junk_vars, tle);
+ }
+
+ /* Check if any of the child junk Vars need any special attention. */
+ foreach(lc, child_junk_vars)
+ {
+ TargetEntry *tle = lfirst(lc);
+
+ Assert(tle->resjunk);
+
+ if (!IsA(tle->expr, Var))
+ elog(ERROR, "UPDATE junk expression added by foreign table %u not suppported",
+ childrte->relid);
+
+ if (need_special_junk_var(root, tle, childrel->relid))
+ add_special_junk_var(root, tle, childrel->relid, rootrel);
+ }
+}
+
+/*
+ * Check if we need to consider the junk TLE required by the given child
+ * target relation a "special" junk var, so as to create a new
+ * SpecialJunkVarInfo for it.
+ *
+ * Side-effect warning: If the TLE is found to match the properties of some
+ * SpecialJunkVarInfo already present in root->specialJunkVars, this adds
+ * child_relid into SpecialJunkVarInfo.child_relids.
+ */
+static bool
+need_special_junk_var(PlannerInfo *root,
+ TargetEntry *tle,
+ Index child_relid)
+{
+ Var *var = (Var *) tle->expr;
+ ListCell *lc;
+
+ Assert(IsA(var, Var));
+
+ /* Maybe a similar looking TLE was already considered one? */
+ foreach(lc, root->specialJunkVars)
+ {
+ SpecialJunkVarInfo *sjv = lfirst(lc);
+
+ if (strcmp(tle->resname, sjv->attrname) == 0 &&
+ var->vartype == sjv->vartype &&
+ var->varattno == sjv->varattno)
+ {
+ sjv->child_relids = bms_add_member(sjv->child_relids, child_relid);
+ return false;
+ }
+ }
+
+ /* It's special if the top-level targetlist doesn't have it. */
+ foreach(lc, root->parse->targetList)
+ {
+ TargetEntry *known_tle = lfirst(lc);
+ Var *known_var = (Var *) known_tle->expr;
+ Oid known_vartype = exprType((Node *) known_var);
+ AttrNumber known_varattno = known_var->varattno;
+
+ /* Ignore non-junk columns. */
+ if (!known_tle->resjunk)
+ continue;
+
+ /* Ignore RETURNING expressions too, if any. */
+ if (known_tle->resname == NULL)
+ continue;
+
+ Assert(IsA(known_var, Var));
+ if (strcmp(tle->resname, known_tle->resname) == 0 &&
+ var->vartype == known_vartype &&
+ var->varattno == known_varattno)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Makes a SpecialJunkVarInfo for a child junk Var that was unlike
+ * any other seen so far.
+ */
+static void
+add_special_junk_var(PlannerInfo *root,
+ TargetEntry *tle,
+ Index child_relid,
+ RelOptInfo *rootrel)
+{
+ SpecialJunkVarInfo *sjv;
+ Var *var = (Var *) tle->expr;
+ Oid vartype = exprType((Node *) var);
+ AttrNumber special_attno;
+ AppendRelInfo *appinfo = root->append_rel_array[child_relid];
+
+ Assert(IsA(var, Var));
+
+ /*
+ * If root parent can emit this var, no need to assign "special" attribute
+ * numbers. Unfortunately, this results in all the children needlessly
+ * emitting the column, even if only this child needs it.
+ */
+ Assert(appinfo != NULL && var->varattno < appinfo->num_child_cols);
+
+ /* Wholerow ok. */
+ if (var->varattno == 0)
+ special_attno = 0;
+ /* A column that is also present in the parent. */
+ else if (appinfo->parent_colnos[var->varattno] > 0)
+ special_attno = appinfo->parent_colnos[var->varattno];
+ /* Nope, use a "special" attribute number. */
+ else
+ special_attno = ++(rootrel->max_attr);
+
+ sjv = makeNode(SpecialJunkVarInfo);
+ sjv->attrname = pstrdup(tle->resname);
+ sjv->vartype = vartype;
+ sjv->vartypmod = exprTypmod((Node *) var);
+ sjv->varcollid = exprCollation((Node *) var);
+ /*
+ * Parent Var with a "special" attribute number is mapped to a child Var
+ * with this attribute number, provided the child's RT index is in
+ * child_relids. For other children, it's mapped to a NULL constant.
+ * See adjust_appendrel_attrs_mutator().
+ */
+ sjv->varattno = var->varattno;
+ sjv->special_attno = special_attno;
+ sjv->child_relids = bms_add_member(NULL, child_relid);
+
+ root->specialJunkVars = lappend(root->specialJunkVars, sjv);
+}
+
+SpecialJunkVarInfo *
+get_special_junk_var(PlannerInfo *root, Index special_attno)
+{
+ ListCell *l;
+
+ foreach(l, root->specialJunkVars)
+ {
+ SpecialJunkVarInfo *sjv = lfirst(l);
+
+ if (sjv->special_attno == special_attno)
+ return sjv;
+ }
+
+ return NULL;
+}
+
+/*
* expand_single_inheritance_child
* Build a RangeTblEntry and an AppendRelInfo, plus maybe a PlanRowMark.
*
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 64da7f5..3f977ba 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3512,8 +3512,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
double total_size;
ListCell *lc;
- Assert(list_length(resultRelations) == list_length(subpaths));
- Assert(list_length(resultRelations) == list_length(subroots));
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a9f833a..c6b83db 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -112,7 +112,7 @@ static void set_baserel_partition_constraint(Relation relation,
*/
void
get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
- RelOptInfo *rel)
+ RelOptInfo *rel, RelOptInfo *parent)
{
Index varno = rel->relid;
Relation relation;
@@ -464,6 +464,16 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
set_relation_partition_info(root, rel, relation);
+ /*
+ * If this appears to be a child of an UPDATE/DELETE parent relation, make
+ * its ResultRelPlanInfo now while we have the relation open.
+ */
+ if (root->parse->resultRelation &&
+ root->parse->commandType != CMD_INSERT &&
+ parent && root->result_rel_array[parent->relid])
+ make_result_relation_info(root, varno, relation,
+ root->result_rel_array[parent->relid]);
+
table_close(relation, NoLock);
/*
@@ -1439,18 +1449,11 @@ relation_excluded_by_constraints(PlannerInfo *root,
/*
* When constraint_exclusion is set to 'partition' we only handle
- * appendrel members. Normally, they are RELOPT_OTHER_MEMBER_REL
- * relations, but we also consider inherited target relations as
- * appendrel members for the purposes of constraint exclusion
- * (since, indeed, they were appendrel members earlier in
- * inheritance_planner).
- *
- * In both cases, partition pruning was already applied, so there
- * is no need to consider the rel's partition constraints here.
+ * appendrel members. Partition pruning has already been applied,
+ * so there is no need to consider the rel's partition constraints
+ * here.
*/
- if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
- (rel->relid == root->parse->resultRelation &&
- root->inhTargetKind != INHKIND_NONE))
+ if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
break; /* appendrel member, so process it */
return false;
@@ -1463,9 +1466,7 @@ relation_excluded_by_constraints(PlannerInfo *root,
* its partition constraints haven't been considered yet, so
* include them in the processing here.
*/
- if (rel->reloptkind == RELOPT_BASEREL &&
- !(rel->relid == root->parse->resultRelation &&
- root->inhTargetKind != INHKIND_NONE))
+ if (rel->reloptkind == RELOPT_BASEREL)
include_partition = true;
break; /* always try to exclude */
}
@@ -2364,24 +2365,51 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
*/
void
make_result_relation_info(PlannerInfo *root,
- Index rti, Relation relation)
+ Index rti, Relation relation,
+ ResultRelPlanInfo *parentInfo)
{
Query *parse = root->parse;
ResultRelPlanInfo *resultInfo = makeNode(ResultRelPlanInfo);
+ AppendRelInfo *appinfo = parentInfo ? root->append_rel_array[rti] :
+ NULL;
List *subplanTargetList;
List *returningList = NIL;
List *updateTargetList = NIL;
List *withCheckOptions = NIL;
- /* parse->targetList resnos get updated later, so make a copy. */
- subplanTargetList = copyObject(parse->targetList);
- withCheckOptions = parse->withCheckOptions;
- returningList = parse->returningList;
+ Assert(appinfo != NULL || parentInfo == NULL);
+ if (parentInfo)
+ {
+ subplanTargetList = (List *)
+ adjust_appendrel_attrs(root, (Node *) parentInfo->subplanTargetList,
+ 1, &appinfo);
+ Assert(appinfo != NULL);
+ if (parentInfo->withCheckOptions)
+ withCheckOptions = (List *)
+ adjust_appendrel_attrs(root,
+ (Node *) parentInfo->withCheckOptions,
+ 1, &appinfo);
+ if (parentInfo->returningList)
+ returningList = (List *)
+ adjust_appendrel_attrs(root, (Node *) parentInfo->returningList,
+ 1, &appinfo);
+ }
+ else
+ {
+ /* parse->targetList resnos get updated later, so make a copy. */
+ subplanTargetList = copyObject(parse->targetList);
+ withCheckOptions = parse->withCheckOptions;
+ returningList = parse->returningList;
+ }
if (parse->commandType == CMD_UPDATE)
{
List *planTargetList = subplanTargetList;
+ /* resnos must match the child attributes numbers. */
+ if (appinfo)
+ planTargetList = adjust_inherited_tlist(planTargetList, appinfo);
+
/* Fix to use plan TLEs over the existing ones. */
updateTargetList = make_update_targetlist(root, rti, relation,
planTargetList);
@@ -2401,4 +2429,16 @@ make_result_relation_info(PlannerInfo *root,
resultInfo->returningList = returningList;
root->result_rel_list = lappend(root->result_rel_list, resultInfo);
+
+ /*
+ * We can get here before query_planner() is called, so the array may
+ * not have been created yet. Worry not, because once it's created,
+ * anything present in result_rel_list at that point will be put into
+ * the array.
+ */
+ if (root->result_rel_array)
+ {
+ Assert(root->result_rel_array[rti] == NULL);
+ root->result_rel_array[rti] = resultInfo;
+ }
}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index a203e6f..7dd903e 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -108,6 +108,20 @@ setup_simple_rel_arrays(PlannerInfo *root)
root->simple_rte_array[rti++] = rte;
}
+ /* For UPDATE/DELETE result relations. */
+ if (root->parse->resultRelation && root->parse->commandType != CMD_INSERT)
+ {
+ root->result_rel_array = (ResultRelPlanInfo **)
+ palloc0(root->simple_rel_array_size * sizeof(ResultRelPlanInfo *));
+ /* Some may have already been added. */
+ foreach(lc, root->result_rel_list)
+ {
+ ResultRelPlanInfo *resultInfo = lfirst(lc);
+
+ root->result_rel_array[resultInfo->resultRelation] = resultInfo;
+ }
+ }
+
/* append_rel_array is not needed if there are no AppendRelInfos */
if (root->append_rel_list == NIL)
{
@@ -183,6 +197,21 @@ expand_planner_arrays(PlannerInfo *root, int add_size)
palloc0(sizeof(AppendRelInfo *) * new_size);
}
+ if (root->result_rel_array)
+ {
+ root->result_rel_array = (ResultRelPlanInfo **)
+ repalloc(root->result_rel_array,
+ sizeof(ResultRelPlanInfo *) * new_size);
+ MemSet(root->result_rel_array + root->simple_rel_array_size,
+ 0, sizeof(ResultRelPlanInfo *) * add_size);
+ }
+ else if (root->parse->resultRelation &&
+ root->parse->commandType != CMD_INSERT)
+ {
+ root->result_rel_array = (ResultRelPlanInfo **)
+ palloc0(sizeof(ResultRelPlanInfo *) * new_size);
+ }
+
root->simple_rel_array_size = new_size;
}
@@ -304,7 +333,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
{
case RTE_RELATION:
/* Table --- retrieve statistics from the system catalogs */
- get_relation_info(root, rte->relid, rte->inh, rel);
+ get_relation_info(root, rte->relid, rte->inh, rel, parent);
break;
case RTE_SUBQUERY:
case RTE_FUNCTION:
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 903b110..1f4efd6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -604,8 +604,8 @@ extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
/* prototypes from functions in nodeModifyTable.c */
-extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *mtstate, Oid reloid,
- int *whichrel);
+extern ResultRelInfo *ExecLookupModifyResultRelByOid(ModifyTableState *mtstate,
+ Oid reloid, int *whichrel);
/* needed by trigger.c */
extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 520ef1b..88ac0b2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1169,15 +1169,25 @@ typedef struct ModifyTableState
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
bool mt_done; /* are we done? */
- PlanState **mt_plans; /* subplans (one per target rel) */
- int mt_nplans; /* number of plans in the array */
- int mt_whichplan; /* which one is being executed (0..n-1) */
- TupleTableSlot **mt_scans; /* input tuple corresponding to underlying
- * plans */
- ResultRelInfo *resultRelInfo; /* per-subplan target relations */
+ PlanState **mt_plans; /* subplans (actually only 1!) */
+ int mt_nplans; /* number of plans in mt_plans (only 1!) */
+ int mt_whichplan; /* which one is being executed (always 0th!) */
+ int mt_nrels; /* number of result rels in the arrays */
+ int mt_whichrel; /* Array index of target rel being targeted */
+ TupleTableSlot **mt_scans; /* input tuple for each result relation */
+
+ /*
+ * For UPDATE and DELETE, resno of the TargetEntry corresponding to
+ * the "tableoid" junk attribute present in the subplan's targetlist.
+ */
+ int mt_tableOidAttno;
+
+ ResultRelInfo *resultRelInfo; /* Target relations */
+ HTAB *mt_subplan_resultrel_hash; /* hash table to look up result
+ * relation by OID. */
ResultRelInfo *rootResultRelInfo; /* root target relation (partitioned
* table root) */
- List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */
+ List **mt_arowmarks; /* ExecAuxRowMark lists (actually only 1!) */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 173f66d..e7d99c8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -270,6 +270,7 @@ typedef enum NodeTag
T_SpecialJoinInfo,
T_AppendRelInfo,
T_ResultRelPlanInfo,
+ T_SpecialJunkVarInfo,
T_PlaceHolderInfo,
T_MinMaxAggInfo,
T_PlannerParamItem,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 3cee3a3..82f024e 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -81,18 +81,6 @@ typedef enum UpperRelationKind
/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
} UpperRelationKind;
-/*
- * This enum identifies which type of relation is being planned through the
- * inheritance planner. INHKIND_NONE indicates the inheritance planner
- * was not used.
- */
-typedef enum InheritanceKind
-{
- INHKIND_NONE,
- INHKIND_INHERITED,
- INHKIND_PARTITIONED
-} InheritanceKind;
-
/*----------
* PlannerGlobal
* Global information for planning/optimization
@@ -222,6 +210,7 @@ struct PlannerInfo
List *result_rel_list; /* List of ResultRelPlanInfo */
/* Same length as other "simple" rel arrays. */
struct ResultRelPlanInfo **result_rel_array;
+ List *specialJunkVars; /* List of SpecialJunkVarInfo */
/*
* all_baserels is a Relids set of all base relids (but not "other"
@@ -344,9 +333,6 @@ struct PlannerInfo
Index qual_security_level; /* minimum security_level for quals */
/* Note: qual_security_level is zero if there are no securityQuals */
- InheritanceKind inhTargetKind; /* indicates if the target relation is an
- * inheritance child or partition or a
- * partitioned table */
bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */
bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */
bool hasHavingQual; /* true if havingQual was non-null */
@@ -2311,6 +2297,54 @@ typedef struct ResultRelPlanInfo
} ResultRelPlanInfo;
/*
+ * SpecialJunkVarInfo
+ *
+ * This contains mapping information about certain junk Vars contained in the
+ * top-level targetlist.
+ *
+ * When updating (or deleting) inheritance hierarchies, some child tables may
+ * require row-identifying junk Vars that are not same as the one installed by
+ * the table being modified (the inheritance root). For each such group of
+ * child junk vars (typically coming from the same FDW), we add a TargetEntry
+ * containing a "special" Var to the top-level target list, while also adding
+ * adding the Var to the root parent's reltarget, and the information to map
+ * the special Var back to the child Var is stored in this node. A list of
+ * these nodes is present in PlannerInfo.specialJunkVars.
+ *
+ * The Var is special, because the parent may not actually be able to produce
+ * a value for it by itself. That is not a problem in practice, because the
+ * actual value for it comes from the child table that introduced such a Var
+ * in the first place. adjust_appendrel_attrs_mutator() which maps the
+ * parent's Vars to a given child's refers to the mapping information here
+ * to convert a special parent Var to the child Var.
+ */
+typedef struct SpecialJunkVarInfo
+{
+ NodeTag type;
+
+ /* TargetEntry resname */
+ char *attrname;
+
+ /* Child Var info */
+ Oid vartype;
+ int vartypmod;
+ Oid varcollid;
+ AttrNumber varattno;
+
+ /*
+ * Special parent attribute number. If not 0, this starts at
+ * parent's RelOptInfo.max_attr + 1.
+ */
+ AttrNumber special_attno;
+
+ /*
+ * RT indexes of all child relations sharing a given instance of this
+ * node.
+ */
+ Relids child_relids;
+} SpecialJunkVarInfo;
+
+/*
* For each distinct placeholder expression generated during planning, we
* store a PlaceHolderInfo node in the PlannerInfo node's placeholder_list.
* This stores info that is needed centrally rather than in each copy of the
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a6..697b9d1 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -22,6 +22,8 @@ extern AppendRelInfo *make_append_rel_info(Relation parentrel,
Index parentRTindex, Index childRTindex);
extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
int nappinfos, AppendRelInfo **appinfos);
+extern Node *adjust_target_appendrel_attrs(PlannerInfo *root, Node *node,
+ AppendRelInfo *appinfo);
extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
Relids child_relids,
Relids top_parent_relids);
@@ -29,6 +31,7 @@ extern Relids adjust_child_relids(Relids relids, int nappinfos,
AppendRelInfo **appinfos);
extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
Relids child_relids, Relids top_parent_relids);
+extern List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context);
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 21caac3..8f684a0 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -320,5 +320,4 @@ extern RelOptInfo *build_child_join_rel(PlannerInfo *root,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
RelOptInfo *parent_joinrel, List *restrictlist,
SpecialJoinInfo *sjinfo, JoinType jointype);
-
#endif /* PATHNODE_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 66dbde2..18f4968 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,9 +26,11 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
- bool inhparent, RelOptInfo *rel);
+ bool inhparent, RelOptInfo *rel,
+ RelOptInfo *parent);
extern void make_result_relation_info(PlannerInfo *root,
- Index rti, Relation relation);
+ Index rti, Relation relation,
+ ResultRelPlanInfo *parentInfo);
extern List *infer_arbiter_indexes(PlannerInfo *root);
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 1d4c7da..02af615 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -54,4 +54,6 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
#define create_pathtarget(root, tlist) \
set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+extern SpecialJunkVarInfo *get_special_junk_var(PlannerInfo *root,
+ Index special_attno);
#endif /* TLIST_H */
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 94e43c3..de7d7c3 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -545,27 +545,25 @@ create table some_tab_child () inherits (some_tab);
insert into some_tab_child values(1,2);
explain (verbose, costs off)
update some_tab set a = a + 1 where false;
- QUERY PLAN
---------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------
Update on public.some_tab
- Update on public.some_tab
-> Result
- Output: (a + 1), ctid
+ Output: (some_tab.a + 1), some_tab.ctid, some_tab.tableoid
One-Time Filter: false
-(5 rows)
+(4 rows)
update some_tab set a = a + 1 where false;
explain (verbose, costs off)
update some_tab set a = a + 1 where false returning b, a;
- QUERY PLAN
---------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------
Update on public.some_tab
- Output: b, a
- Update on public.some_tab
+ Output: some_tab.b, some_tab.a
-> Result
- Output: (a + 1), ctid
+ Output: (some_tab.a + 1), some_tab.ctid, some_tab.tableoid
One-Time Filter: false
-(6 rows)
+(5 rows)
update some_tab set a = a + 1 where false returning b, a;
b | a
@@ -670,7 +668,7 @@ explain update parted_tab set a = 2 where false;
QUERY PLAN
--------------------------------------------------------
Update on parted_tab (cost=0.00..0.00 rows=0 width=0)
- -> Result (cost=0.00..0.00 rows=0 width=0)
+ -> Result (cost=0.00..0.00 rows=0 width=14)
One-Time Filter: false
(3 rows)
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index b45a590..b1bd9dc 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -1926,37 +1926,27 @@ WHERE EXISTS (
FROM int4_tbl,
LATERAL (SELECT int4_tbl.f1 FROM int8_tbl LIMIT 2) ss
WHERE prt1_l.c IS NULL);
- QUERY PLAN
----------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------
Delete on prt1_l
Delete on prt1_l_p1 prt1_l_1
Delete on prt1_l_p3_p1 prt1_l_2
Delete on prt1_l_p3_p2 prt1_l_3
-> Nested Loop Semi Join
- -> Seq Scan on prt1_l_p1 prt1_l_1
- Filter: (c IS NULL)
- -> Nested Loop
- -> Seq Scan on int4_tbl
- -> Subquery Scan on ss
- -> Limit
- -> Seq Scan on int8_tbl
- -> Nested Loop Semi Join
- -> Seq Scan on prt1_l_p3_p1 prt1_l_2
- Filter: (c IS NULL)
- -> Nested Loop
- -> Seq Scan on int4_tbl
- -> Subquery Scan on ss_1
- -> Limit
- -> Seq Scan on int8_tbl int8_tbl_1
- -> Nested Loop Semi Join
- -> Seq Scan on prt1_l_p3_p2 prt1_l_3
- Filter: (c IS NULL)
- -> Nested Loop
- -> Seq Scan on int4_tbl
- -> Subquery Scan on ss_2
- -> Limit
- -> Seq Scan on int8_tbl int8_tbl_2
-(28 rows)
+ -> Append
+ -> Seq Scan on prt1_l_p1 prt1_l_1
+ Filter: (c IS NULL)
+ -> Seq Scan on prt1_l_p3_p1 prt1_l_2
+ Filter: (c IS NULL)
+ -> Seq Scan on prt1_l_p3_p2 prt1_l_3
+ Filter: (c IS NULL)
+ -> Materialize
+ -> Nested Loop
+ -> Seq Scan on int4_tbl
+ -> Subquery Scan on ss
+ -> Limit
+ -> Seq Scan on int8_tbl
+(18 rows)
--
-- negative testcases
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 4315e8e..a295c08 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2435,74 +2435,43 @@ deallocate ab_q6;
insert into ab values (1,2);
explain (analyze, costs off, summary off, timing off)
update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
- QUERY PLAN
--------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
Update on ab_a1 (actual rows=0 loops=1)
Update on ab_a1_b1 ab_a1_1
Update on ab_a1_b2 ab_a1_2
Update on ab_a1_b3 ab_a1_3
- -> Nested Loop (actual rows=0 loops=1)
- -> Append (actual rows=1 loops=1)
- -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
- Recheck Cond: (a = 1)
- Heap Blocks: exact=1
- -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1)
- Index Cond: (a = 1)
- -> Materialize (actual rows=0 loops=1)
- -> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
- Index Cond: (a = 1)
-> Nested Loop (actual rows=1 loops=1)
-> Append (actual rows=1 loops=1)
- -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
- Recheck Cond: (a = 1)
- Heap Blocks: exact=1
- -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Materialize (actual rows=1 loops=1)
-> Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
- -> Nested Loop (actual rows=0 loops=1)
- -> Append (actual rows=1 loops=1)
- -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
- Recheck Cond: (a = 1)
- Heap Blocks: exact=1
- -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Materialize (actual rows=0 loops=1)
-> Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-(65 rows)
+ -> Materialize (actual rows=1 loops=1)
+ -> Append (actual rows=1 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
+ Recheck Cond: (a = 1)
+ Heap Blocks: exact=1
+ -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
+ Index Cond: (a = 1)
+(34 rows)
table ab;
a | b
@@ -2523,29 +2492,12 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
Update on ab_a1_b3 ab_a1_3
InitPlan 1 (returns $0)
-> Result (actual rows=1 loops=1)
- -> Nested Loop (actual rows=1 loops=1)
- -> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
- -> Materialize (actual rows=1 loops=1)
- -> Append (actual rows=1 loops=1)
- -> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
- Filter: (b = $0)
- -> Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
- Filter: (b = $0)
- -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
- Filter: (b = $0)
- -> Nested Loop (actual rows=1 loops=1)
- -> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
- -> Materialize (actual rows=1 loops=1)
- -> Append (actual rows=1 loops=1)
- -> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
- Filter: (b = $0)
- -> Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
- Filter: (b = $0)
- -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
- Filter: (b = $0)
- -> Nested Loop (actual rows=1 loops=1)
- -> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
- -> Materialize (actual rows=1 loops=1)
+ -> Nested Loop (actual rows=3 loops=1)
+ -> Append (actual rows=3 loops=1)
+ -> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
+ -> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
+ -> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
+ -> Materialize (actual rows=1 loops=3)
-> Append (actual rows=1 loops=1)
-> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
Filter: (b = $0)
@@ -2553,7 +2505,7 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
Filter: (b = $0)
-> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
Filter: (b = $0)
-(36 rows)
+(19 rows)
select tableoid::regclass, * from ab;
tableoid | a | b
@@ -3392,28 +3344,30 @@ explain (costs off) select * from pp_lp where a = 1;
(5 rows)
explain (costs off) update pp_lp set value = 10 where a = 1;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+----------------------------------------
Update on pp_lp
Update on pp_lp1 pp_lp_1
Update on pp_lp2 pp_lp_2
- -> Seq Scan on pp_lp1 pp_lp_1
- Filter: (a = 1)
- -> Seq Scan on pp_lp2 pp_lp_2
- Filter: (a = 1)
-(7 rows)
+ -> Append
+ -> Seq Scan on pp_lp1 pp_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ Filter: (a = 1)
+(8 rows)
explain (costs off) delete from pp_lp where a = 1;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+----------------------------------------
Delete on pp_lp
Delete on pp_lp1 pp_lp_1
Delete on pp_lp2 pp_lp_2
- -> Seq Scan on pp_lp1 pp_lp_1
- Filter: (a = 1)
- -> Seq Scan on pp_lp2 pp_lp_2
- Filter: (a = 1)
-(7 rows)
+ -> Append
+ -> Seq Scan on pp_lp1 pp_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ Filter: (a = 1)
+(8 rows)
set constraint_exclusion = 'off'; -- this should not affect the result.
explain (costs off) select * from pp_lp where a = 1;
@@ -3427,28 +3381,30 @@ explain (costs off) select * from pp_lp where a = 1;
(5 rows)
explain (costs off) update pp_lp set value = 10 where a = 1;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+----------------------------------------
Update on pp_lp
Update on pp_lp1 pp_lp_1
Update on pp_lp2 pp_lp_2
- -> Seq Scan on pp_lp1 pp_lp_1
- Filter: (a = 1)
- -> Seq Scan on pp_lp2 pp_lp_2
- Filter: (a = 1)
-(7 rows)
+ -> Append
+ -> Seq Scan on pp_lp1 pp_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ Filter: (a = 1)
+(8 rows)
explain (costs off) delete from pp_lp where a = 1;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+----------------------------------------
Delete on pp_lp
Delete on pp_lp1 pp_lp_1
Delete on pp_lp2 pp_lp_2
- -> Seq Scan on pp_lp1 pp_lp_1
- Filter: (a = 1)
- -> Seq Scan on pp_lp2 pp_lp_2
- Filter: (a = 1)
-(7 rows)
+ -> Append
+ -> Seq Scan on pp_lp1 pp_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ Filter: (a = 1)
+(8 rows)
drop table pp_lp;
-- Ensure enable_partition_prune does not affect non-partitioned tables.
@@ -3472,28 +3428,31 @@ explain (costs off) select * from inh_lp where a = 1;
(5 rows)
explain (costs off) update inh_lp set value = 10 where a = 1;
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+------------------------------------------------
Update on inh_lp
- Update on inh_lp
- Update on inh_lp1 inh_lp_1
- -> Seq Scan on inh_lp
- Filter: (a = 1)
- -> Seq Scan on inh_lp1 inh_lp_1
- Filter: (a = 1)
-(7 rows)
+ Update on inh_lp inh_lp_1
+ Update on inh_lp1 inh_lp_2
+ -> Result
+ -> Append
+ -> Seq Scan on inh_lp inh_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on inh_lp1 inh_lp_2
+ Filter: (a = 1)
+(9 rows)
explain (costs off) delete from inh_lp where a = 1;
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+------------------------------------------
Delete on inh_lp
- Delete on inh_lp
- Delete on inh_lp1 inh_lp_1
- -> Seq Scan on inh_lp
- Filter: (a = 1)
- -> Seq Scan on inh_lp1 inh_lp_1
- Filter: (a = 1)
-(7 rows)
+ Delete on inh_lp inh_lp_1
+ Delete on inh_lp1 inh_lp_2
+ -> Append
+ -> Seq Scan on inh_lp inh_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on inh_lp1 inh_lp_2
+ Filter: (a = 1)
+(8 rows)
-- Ensure we don't exclude normal relations when we only expect to exclude
-- inheritance children
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 9506aae..b02a682 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1632,19 +1632,21 @@ EXPLAIN (COSTS OFF) EXECUTE p2(2);
--
SET SESSION AUTHORIZATION regress_rls_bob;
EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
- QUERY PLAN
------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------
Update on t1
- Update on t1
- Update on t2 t1_1
- Update on t3 t1_2
- -> Seq Scan on t1
- Filter: (((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_1
- Filter: (((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2
- Filter: (((a % 2) = 0) AND f_leak(b))
-(10 rows)
+ Update on t1 t1_1
+ Update on t2 t1_2
+ Update on t3 t1_3
+ -> Result
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(12 rows)
UPDATE t1 SET b = b || b WHERE f_leak(b);
NOTICE: f_leak => bbb
@@ -1722,31 +1724,27 @@ NOTICE: f_leak => cde
NOTICE: f_leak => yyyyyy
EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
- QUERY PLAN
------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------
Update on t1
- Update on t1
- Update on t2 t1_1
- Update on t3 t1_2
- -> Nested Loop
- -> Seq Scan on t1
- Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2
- Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
- -> Nested Loop
- -> Seq Scan on t2 t1_1
- Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2
- Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ Update on t1 t1_1
+ Update on t2 t1_2
+ Update on t3 t1_3
-> Nested Loop
- -> Seq Scan on t3 t1_2
- Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2
Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
-(19 rows)
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+(14 rows)
UPDATE t1 SET b=t1.b FROM t2
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+NOTICE: f_leak => cde
EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
QUERY PLAN
@@ -1795,46 +1793,30 @@ NOTICE: f_leak => cde
EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
- QUERY PLAN
------------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------------
Update on t1 t1_1
- Update on t1 t1_1
- Update on t2 t1_1_1
- Update on t3 t1_1_2
+ Update on t1 t1_1_1
+ Update on t2 t1_1_2
+ Update on t3 t1_1_3
-> Nested Loop
Join Filter: (t1_1.b = t1_2.b)
- -> Seq Scan on t1 t1_1
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Append
- -> Seq Scan on t1 t1_2_1
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_2_2
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2_3
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Nested Loop
- Join Filter: (t1_1_1.b = t1_2.b)
- -> Seq Scan on t2 t1_1_1
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Append
- -> Seq Scan on t1 t1_2_1
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_2_2
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2_3
+ -> Seq Scan on t1 t1_1_1
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Nested Loop
- Join Filter: (t1_1_2.b = t1_2.b)
- -> Seq Scan on t3 t1_1_2
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Append
- -> Seq Scan on t1 t1_2_1
+ -> Seq Scan on t2 t1_1_2
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_2_2
+ -> Seq Scan on t3 t1_1_3
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2_3
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-(37 rows)
+ -> Materialize
+ -> Append
+ -> Seq Scan on t1 t1_2_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2_2
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_2_3
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+(21 rows)
UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
@@ -1843,8 +1825,6 @@ NOTICE: f_leak => daddad_updt
NOTICE: f_leak => daddad_updt
NOTICE: f_leak => defdef
NOTICE: f_leak => defdef
-NOTICE: f_leak => daddad_updt
-NOTICE: f_leak => defdef
id | a | b | id | a | b | t1_1 | t1_2
-----+---+-------------+-----+---+-------------+---------------------+---------------------
104 | 4 | daddad_updt | 104 | 4 | daddad_updt | (104,4,daddad_updt) | (104,4,daddad_updt)
@@ -1880,19 +1860,20 @@ EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
(3 rows)
EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
- QUERY PLAN
------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------
Delete on t1
- Delete on t1
- Delete on t2 t1_1
- Delete on t3 t1_2
- -> Seq Scan on t1
- Filter: (((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_1
- Filter: (((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2
- Filter: (((a % 2) = 0) AND f_leak(b))
-(10 rows)
+ Delete on t1 t1_1
+ Delete on t2 t1_2
+ Delete on t3 t1_3
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(11 rows)
DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
NOTICE: f_leak => bbbbbb_updt
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 13d0d2a..05ee269 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1572,26 +1572,21 @@ UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
QUERY PLAN
-------------------------------------------------------------------------
Update on base_tbl_parent
- Update on base_tbl_parent
- Update on base_tbl_child base_tbl_parent_1
- -> Hash Join
- Hash Cond: (other_tbl_parent.id = base_tbl_parent.a)
- -> Append
- -> Seq Scan on other_tbl_parent other_tbl_parent_1
- -> Seq Scan on other_tbl_child other_tbl_parent_2
- -> Hash
- -> Seq Scan on base_tbl_parent
+ Update on base_tbl_parent base_tbl_parent_1
+ Update on base_tbl_child base_tbl_parent_2
-> Merge Join
- Merge Cond: (base_tbl_parent_1.a = other_tbl_parent.id)
+ Merge Cond: (base_tbl_parent.a = other_tbl_parent.id)
-> Sort
- Sort Key: base_tbl_parent_1.a
- -> Seq Scan on base_tbl_child base_tbl_parent_1
+ Sort Key: base_tbl_parent.a
+ -> Append
+ -> Seq Scan on base_tbl_parent base_tbl_parent_1
+ -> Seq Scan on base_tbl_child base_tbl_parent_2
-> Sort
Sort Key: other_tbl_parent.id
-> Append
-> Seq Scan on other_tbl_parent other_tbl_parent_1
-> Seq Scan on other_tbl_child other_tbl_parent_2
-(20 rows)
+(15 rows)
UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
@@ -2301,42 +2296,45 @@ SELECT * FROM v1 WHERE a=8;
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------
Update on public.t1
- Update on public.t1
- Update on public.t11 t1_1
- Update on public.t12 t1_2
- Update on public.t111 t1_3
- -> Index Scan using t1_a_idx on public.t1
- Output: 100, t1.ctid
- Index Cond: ((t1.a > 5) AND (t1.a < 7))
- Filter: ((t1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
- SubPlan 1
- -> Append
- -> Seq Scan on public.t12 t12_1
- Filter: (t12_1.a = t1.a)
- -> Seq Scan on public.t111 t12_2
- Filter: (t12_2.a = t1.a)
- SubPlan 2
- -> Append
- -> Seq Scan on public.t12 t12_4
- Output: t12_4.a
- -> Seq Scan on public.t111 t12_5
- Output: t12_5.a
- -> Index Scan using t11_a_idx on public.t11 t1_1
- Output: 100, t1_1.ctid
- Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
- Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
- -> Index Scan using t12_a_idx on public.t12 t1_2
- Output: 100, t1_2.ctid
- Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
- Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
- -> Index Scan using t111_a_idx on public.t111 t1_3
- Output: 100, t1_3.ctid
- Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
- Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(33 rows)
+ Update on public.t1 t1_1
+ Update on public.t11 t1_2
+ Update on public.t12 t1_3
+ Update on public.t111 t1_4
+ -> Result
+ Output: 100, t1.ctid, t1.tableoid
+ -> Append
+ -> Index Scan using t1_a_idx on public.t1 t1_1
+ Output: t1_1.ctid, t1_1.tableoid
+ Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
+ Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on public.t12 t12_1
+ Filter: (t12_1.a = t1_1.a)
+ -> Seq Scan on public.t111 t12_2
+ Filter: (t12_2.a = t1_1.a)
+ SubPlan 2
+ -> Append
+ -> Seq Scan on public.t12 t12_4
+ Output: t12_4.a
+ -> Seq Scan on public.t111 t12_5
+ Output: t12_5.a
+ -> Index Scan using t11_a_idx on public.t11 t1_2
+ Output: t1_2.ctid, t1_2.tableoid
+ Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
+ Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+ -> Index Scan using t12_a_idx on public.t12 t1_3
+ Output: t1_3.ctid, t1_3.tableoid
+ Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
+ Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+ -> Index Scan using t111_a_idx on public.t111 t1_4
+ Output: t1_4.ctid, t1_4.tableoid
+ Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
+ Filter: ((t1_4.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(36 rows)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
@@ -2351,42 +2349,45 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------
Update on public.t1
- Update on public.t1
- Update on public.t11 t1_1
- Update on public.t12 t1_2
- Update on public.t111 t1_3
- -> Index Scan using t1_a_idx on public.t1
- Output: (t1.a + 1), t1.ctid
- Index Cond: ((t1.a > 5) AND (t1.a = 8))
- Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
- SubPlan 1
- -> Append
- -> Seq Scan on public.t12 t12_1
- Filter: (t12_1.a = t1.a)
- -> Seq Scan on public.t111 t12_2
- Filter: (t12_2.a = t1.a)
- SubPlan 2
- -> Append
- -> Seq Scan on public.t12 t12_4
- Output: t12_4.a
- -> Seq Scan on public.t111 t12_5
- Output: t12_5.a
- -> Index Scan using t11_a_idx on public.t11 t1_1
- Output: (t1_1.a + 1), t1_1.ctid
- Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
- Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
- -> Index Scan using t12_a_idx on public.t12 t1_2
- Output: (t1_2.a + 1), t1_2.ctid
- Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
- Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
- -> Index Scan using t111_a_idx on public.t111 t1_3
- Output: (t1_3.a + 1), t1_3.ctid
- Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
- Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(33 rows)
+ Update on public.t1 t1_1
+ Update on public.t11 t1_2
+ Update on public.t12 t1_3
+ Update on public.t111 t1_4
+ -> Result
+ Output: (t1.a + 1), t1.ctid, t1.tableoid
+ -> Append
+ -> Index Scan using t1_a_idx on public.t1 t1_1
+ Output: t1_1.a, t1_1.ctid, t1_1.tableoid
+ Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
+ Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on public.t12 t12_1
+ Filter: (t12_1.a = t1_1.a)
+ -> Seq Scan on public.t111 t12_2
+ Filter: (t12_2.a = t1_1.a)
+ SubPlan 2
+ -> Append
+ -> Seq Scan on public.t12 t12_4
+ Output: t12_4.a
+ -> Seq Scan on public.t111 t12_5
+ Output: t12_5.a
+ -> Index Scan using t11_a_idx on public.t11 t1_2
+ Output: t1_2.a, t1_2.ctid, t1_2.tableoid
+ Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
+ Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+ -> Index Scan using t12_a_idx on public.t12 t1_3
+ Output: t1_3.a, t1_3.ctid, t1_3.tableoid
+ Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
+ Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+ -> Index Scan using t111_a_idx on public.t111 t1_4
+ Output: t1_4.a, t1_4.ctid, t1_4.tableoid
+ Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
+ Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(36 rows)
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
NOTICE: snooped value: 8
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index dece036..dc34ac6 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -308,8 +308,8 @@ ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FOR VALUES FROM (1) TO
-- The order of subplans should be in bound order
EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
- QUERY PLAN
--------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------
Update on range_parted
Update on part_a_1_a_10 range_parted_1
Update on part_a_10_a_20 range_parted_2
@@ -318,21 +318,22 @@ EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
Update on part_d_1_15 range_parted_5
Update on part_d_15_20 range_parted_6
Update on part_b_20_b_30 range_parted_7
- -> Seq Scan on part_a_1_a_10 range_parted_1
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_a_10_a_20 range_parted_2
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_b_1_b_10 range_parted_3
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_c_1_100 range_parted_4
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_d_1_15 range_parted_5
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_d_15_20 range_parted_6
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_b_20_b_30 range_parted_7
- Filter: (c > '97'::numeric)
-(22 rows)
+ -> Append
+ -> Seq Scan on part_a_1_a_10 range_parted_1
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_a_10_a_20 range_parted_2
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_b_1_b_10 range_parted_3
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_c_1_100 range_parted_4
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_d_1_15 range_parted_5
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_d_15_20 range_parted_6
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_b_20_b_30 range_parted_7
+ Filter: (c > '97'::numeric)
+(23 rows)
-- fail, row movement happens only within the partition subtree.
UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 67eaeb4..ad793a2 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -2181,47 +2181,35 @@ SELECT * FROM parent;
EXPLAIN (VERBOSE, COSTS OFF)
WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
DELETE FROM a USING wcte WHERE aa = q2;
- QUERY PLAN
-----------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------
Delete on public.a
- Delete on public.a
- Delete on public.b a_1
- Delete on public.c a_2
- Delete on public.d a_3
+ Delete on public.a a_1
+ Delete on public.b a_2
+ Delete on public.c a_3
+ Delete on public.d a_4
CTE wcte
-> Insert on public.int8_tbl
Output: int8_tbl.q2
-> Result
Output: '42'::bigint, '47'::bigint
- -> Nested Loop
- Output: a.ctid, wcte.*
- Join Filter: (a.aa = wcte.q2)
- -> Seq Scan on public.a
- Output: a.ctid, a.aa
- -> CTE Scan on wcte
+ -> Hash Join
+ Output: a.ctid, wcte.*, a.tableoid
+ Hash Cond: (a.aa = wcte.q2)
+ -> Append
+ -> Seq Scan on public.a a_1
+ Output: a_1.ctid, a_1.aa, a_1.tableoid
+ -> Seq Scan on public.b a_2
+ Output: a_2.ctid, a_2.aa, a_2.tableoid
+ -> Seq Scan on public.c a_3
+ Output: a_3.ctid, a_3.aa, a_3.tableoid
+ -> Seq Scan on public.d a_4
+ Output: a_4.ctid, a_4.aa, a_4.tableoid
+ -> Hash
Output: wcte.*, wcte.q2
- -> Nested Loop
- Output: a_1.ctid, wcte.*
- Join Filter: (a_1.aa = wcte.q2)
- -> Seq Scan on public.b a_1
- Output: a_1.ctid, a_1.aa
- -> CTE Scan on wcte
- Output: wcte.*, wcte.q2
- -> Nested Loop
- Output: a_2.ctid, wcte.*
- Join Filter: (a_2.aa = wcte.q2)
- -> Seq Scan on public.c a_2
- Output: a_2.ctid, a_2.aa
- -> CTE Scan on wcte
- Output: wcte.*, wcte.q2
- -> Nested Loop
- Output: a_3.ctid, wcte.*
- Join Filter: (a_3.aa = wcte.q2)
- -> Seq Scan on public.d a_3
- Output: a_3.ctid, a_3.aa
- -> CTE Scan on wcte
- Output: wcte.*, wcte.q2
-(38 rows)
+ -> CTE Scan on wcte
+ Output: wcte.*, wcte.q2
+(26 rows)
-- error cases
-- data-modifying WITH tries to use its own output
--
1.8.3.1
On 1 Jul 2020, at 15:38, Amit Langote <amitlangote09@gmail.com> wrote:
Another thing I could do is decouple the patches to discuss here from
the patches of the other thread, which should be possible and might be
good to avoid back and forth between the two threads.
It sounds like it would make it easier for reviewers, so if it's possible with
a reasonable effort it might be worth it. I've moved this entry to the next CF
for now.
cheers ./daniel
On Fri, Jun 26, 2020 at 8:36 AM Amit Langote <amitlangote09@gmail.com> wrote:
0001 and 0002 are preparatory patches.
I read through these patches a bit but it's really unclear what the
point of them is. I think they need better commit messages, or better
comments, or both.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Sat, Aug 1, 2020 at 4:46 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jun 26, 2020 at 8:36 AM Amit Langote <amitlangote09@gmail.com> wrote:
0001 and 0002 are preparatory patches.
I read through these patches a bit but it's really unclear what the
point of them is. I think they need better commit messages, or better
comments, or both.
Thanks for taking a look. Sorry about the lack of good commentary,
which I have tried to address in the attached updated version. I
extracted one more part as preparatory from the earlier 0003 patch, so
there are 4 patches now.
Also as discussed with Daniel, I have changed the patches so that they
can be applied on plain HEAD instead of having to first apply the
patches at [1]/messages/by-id/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com. Without runtime pruning for UPDATE/DELETE proposed in
[1]: /messages/by-id/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
performance/scalability by that much, but the benefit of lazily
creating ResultRelInfos seems clear so I think maybe it's okay to
pursue this independently.
--
Amit Langote
EnterpriseDB: http://www.enterprisedb.com
[1]: /messages/by-id/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
Attachments:
v3-0003-Revise-child-to-root-tuple-conversion-map-managem.patchapplication/octet-stream; name=v3-0003-Revise-child-to-root-tuple-conversion-map-managem.patchDownload
From 6086f7ab91db35f2800511f31b85a53de985cd57 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 30 Jul 2019 10:51:35 +0900
Subject: [PATCH v3 3/4] Revise child-to-root tuple conversion map management
Transition tuple capture requires to convert child tuples to the
inheritance root table format because that's the format the
transition tuplestore stores tuple in. For INSERTs into partitioned
tables, the conversion is handled by tuple routing code which
constructs the map for a given partition only if the partition is
targeted, but for UPDATE and DELETE, maps for all result relations
are made and stored in an array in ModifyTableState during
ExecInitModifyTable, which requires their ResultRelInfos to have been
already built. During execution, map for the currently active result
relation is set in TransitionCaptureState.tcs_map.
This commit removes TransitionCaptureMap.tcs_map in favor a new
map field in ResultRelInfo named ri_ChildToRootMap that is
initialized when the ResultRelInfo for a given result relation is.
This way is less confusing and less bug-prone than setting and
resetting tcs_map. Also, this will also allow us to delay creating
the map for a given result relation to when that relation is actually
processed during execution.
---
src/backend/commands/copy.c | 30 +----
src/backend/commands/trigger.c | 9 +-
src/backend/executor/execPartition.c | 20 +++-
src/backend/executor/nodeModifyTable.c | 203 ++++++++-------------------------
src/include/commands/trigger.h | 10 +-
src/include/nodes/execnodes.h | 11 +-
6 files changed, 85 insertions(+), 198 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db7d24a..155ac5b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3117,32 +3117,14 @@ CopyFrom(CopyState cstate)
estate->es_result_relation_info = resultRelInfo;
/*
- * If we're capturing transition tuples, we might need to convert
- * from the partition rowtype to root rowtype.
+ * If we're capturing transition tuples and there are no BEFORE
+ * triggers on the partition which may change the tuple, we can
+ * just remember the original unconverted tuple to avoid a
+ * needless round trip conversion.
*/
if (cstate->transition_capture != NULL)
- {
- if (has_before_insert_row_trig)
- {
- /*
- * If there are any BEFORE triggers on the partition,
- * we'll have to be ready to convert their result back to
- * tuplestore format.
- */
- cstate->transition_capture->tcs_original_insert_tuple = NULL;
- cstate->transition_capture->tcs_map =
- resultRelInfo->ri_PartitionInfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted
- * tuple, to avoid a needless round trip conversion.
- */
- cstate->transition_capture->tcs_original_insert_tuple = myslot;
- cstate->transition_capture->tcs_map = NULL;
- }
- }
+ cstate->transition_capture->tcs_original_insert_tuple =
+ !has_before_insert_row_trig ? myslot : NULL;
/*
* We might need to convert from the root rowtype to the partition
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..d1b5a03 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -35,6 +35,7 @@
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
+#include "executor/execPartition.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
@@ -4293,8 +4294,8 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
* tables, then return NULL.
*
* The resulting object can be passed to the ExecAR* functions. The caller
- * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing
- * with child tables.
+ * should set tcs_original_insert_tuple as appropriate when dealing with child
+ * tables
*
* Note that we copy the flags from a parent table into this struct (rather
* than subsequently using the relation's TriggerDesc directly) so that we can
@@ -5389,7 +5390,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = transition_capture->tcs_map;
+ PartitionRoutingInfo *pinfo = relinfo->ri_PartitionInfo;
+ TupleConversionMap *map = pinfo ? pinfo->pi_PartitionToRootMap :
+ relinfo->ri_ChildToRootMap;
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index ef17acd..7ee2dd9 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -926,9 +926,23 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
if (mtstate &&
(mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
{
- partrouteinfo->pi_PartitionToRootMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
- RelationGetDescr(partRelInfo->ri_PartitionRoot));
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+
+ /*
+ * If the partition appears to be a reused UPDATE result relation, the
+ * necessary map would already have been set in ri_ChildToRootMap by
+ * ExecInitModifyTable(), so use that one instead of building one from
+ * scratch. One can tell if it's actually a reused UPDATE result
+ * relation by looking at its ri_RangeTableIndex which must be
+ * different from the root RT index.
+ */
+ if (node && node->operation == CMD_UPDATE &&
+ node->rootRelation != partRelInfo->ri_RangeTableIndex)
+ partrouteinfo->pi_PartitionToRootMap = partRelInfo->ri_ChildToRootMap;
+ else
+ partrouteinfo->pi_PartitionToRootMap =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
+ RelationGetDescr(partRelInfo->ri_PartitionRoot));
}
else
partrouteinfo->pi_PartitionToRootMap = NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 09a9871..f8f4254 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,9 +72,6 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
-static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
-static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
- int whichplan);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1080,7 +1077,6 @@ ExecUpdate(ModifyTableState *mtstate,
TM_Result result;
TM_FailureData tmfd;
List *recheckIndexes = NIL;
- TupleConversionMap *saved_tcs_map = NULL;
/*
* abort the operation if not running transactions
@@ -1205,7 +1201,6 @@ lreplace:;
TupleTableSlot *ret_slot;
TupleTableSlot *epqslot = NULL;
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
- int map_index;
TupleConversionMap *tupconv_map;
/*
@@ -1275,26 +1270,11 @@ lreplace:;
}
/*
- * Updates set the transition capture map only when a new subplan
- * is chosen. But for inserts, it is set for each row. So after
- * INSERT, we need to revert back to the map created for UPDATE;
- * otherwise the next UPDATE will incorrectly use the one created
- * for INSERT. So first save the one created for UPDATE.
- */
- if (mtstate->mt_transition_capture)
- saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
-
- /*
* resultRelInfo is one of the per-subplan resultRelInfos. So we
* should convert the tuple into root's tuple descriptor, since
- * ExecInsert() starts the search from root. The tuple conversion
- * map list is in the order of mtstate->resultRelInfo[], so to
- * retrieve the one for this resultRel, we need to know the
- * position of the resultRel in mtstate->resultRelInfo[].
+ * ExecInsert() starts the search from root.
*/
- map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
- tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
+ tupconv_map = resultRelInfo->ri_ChildToRootMap;
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1313,11 +1293,13 @@ lreplace:;
/* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
+
+ /*
+ * Reset the transition state that may possibly have been written
+ * by INSERT.
+ */
if (mtstate->mt_transition_capture)
- {
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map = saved_tcs_map;
- }
return ret_slot;
}
@@ -1837,28 +1819,6 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
RelationGetRelid(targetRelInfo->ri_RelationDesc),
CMD_UPDATE);
-
- /*
- * If we found that we need to collect transition tuples then we may also
- * need tuple conversion maps for any children that have TupleDescs that
- * aren't compatible with the tuplestores. (We can share these maps
- * between the regular and ON CONFLICT cases.)
- */
- if (mtstate->mt_transition_capture != NULL ||
- mtstate->mt_oc_transition_capture != NULL)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
-
- /*
- * Install the conversion map for the first plan for UPDATE and DELETE
- * operations. It will be advanced each time we switch to the next
- * plan. (INSERT operations set it every time, so we need not update
- * mtstate->mt_oc_transition_capture here.)
- */
- if (mtstate->mt_transition_capture && mtstate->operation != CMD_INSERT)
- mtstate->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(mtstate, 0);
- }
}
/*
@@ -1882,6 +1842,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *partrel;
PartitionRoutingInfo *partrouteinfo;
TupleConversionMap *map;
+ bool has_before_insert_row_trig;
/*
* Lookup the target partition's ResultRelInfo. If ExecFindPartition does
@@ -1900,37 +1861,15 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
estate->es_result_relation_info = partrel;
/*
- * If we're capturing transition tuples, we might need to convert from the
- * partition rowtype to root partitioned table's rowtype.
+ * If we're capturing transition tuples and there are no BEFORE triggers
+ * on the partition which may change the tuple, we can just remember the
+ * original unconverted tuple to avoid a needless round trip conversion.
*/
+ has_before_insert_row_trig = (partrel->ri_TrigDesc &&
+ partrel->ri_TrigDesc->trig_insert_before_row);
if (mtstate->mt_transition_capture != NULL)
- {
- if (partrel->ri_TrigDesc &&
- partrel->ri_TrigDesc->trig_insert_before_row)
- {
- /*
- * If there are any BEFORE triggers on the partition, we'll have
- * to be ready to convert their result back to tuplestore format.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted tuple, to
- * avoid a needless round trip conversion.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
- mtstate->mt_transition_capture->tcs_map = NULL;
- }
- }
- if (mtstate->mt_oc_transition_capture != NULL)
- {
- mtstate->mt_oc_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
+ mtstate->mt_transition_capture->tcs_original_insert_tuple =
+ !has_before_insert_row_trig ? slot : NULL;
/*
* Convert the tuple, if necessary.
@@ -1946,58 +1885,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
return slot;
}
-/*
- * Initialize the child-to-root tuple conversion map array for UPDATE subplans.
- *
- * This map array is required to convert the tuple from the subplan result rel
- * to the target table descriptor. This requirement arises for two independent
- * scenarios:
- * 1. For update-tuple-routing.
- * 2. For capturing tuples in transition tables.
- */
-static void
-ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
-{
- ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
- ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
- TupleDesc outdesc;
- int numResultRelInfos = mtstate->mt_nplans;
- int i;
-
- /*
- * Build array of conversion maps from each child's TupleDesc to the one
- * used in the target relation. The map pointers may be NULL when no
- * conversion is necessary, which is hopefully a common case.
- */
-
- /* Get tuple descriptor of the target rel. */
- outdesc = RelationGetDescr(targetRelInfo->ri_RelationDesc);
-
- mtstate->mt_per_subplan_tupconv_maps = (TupleConversionMap **)
- palloc(sizeof(TupleConversionMap *) * numResultRelInfos);
-
- for (i = 0; i < numResultRelInfos; ++i)
- {
- mtstate->mt_per_subplan_tupconv_maps[i] =
- convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
- outdesc);
- }
-}
-
-/*
- * For a given subplan index, get the tuple conversion map.
- */
-static TupleConversionMap *
-tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
-{
- /* If nobody else set the per-subplan array of maps, do so ourselves. */
- if (mtstate->mt_per_subplan_tupconv_maps == NULL)
- ExecSetupChildParentMapForSubplan(mtstate);
-
- Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
- return mtstate->mt_per_subplan_tupconv_maps[whichplan];
-}
-
/* ----------------------------------------------------------------
* ExecModifyTable
*
@@ -2107,17 +1994,6 @@ ExecModifyTable(PlanState *pstate)
estate->es_result_relation_info = resultRelInfo;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
- /* Prepare to convert transition tuples from this child. */
- if (node->mt_transition_capture != NULL)
- {
- node->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichplan);
- }
- if (node->mt_oc_transition_capture != NULL)
- {
- node->mt_oc_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichplan);
- }
continue;
}
else
@@ -2298,6 +2174,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
int i;
Relation rel;
bool update_tuple_routing_needed = node->partColsUpdated;
+ ResultRelInfo *rootResultRel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -2320,8 +2197,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
/* If modifying a partitioned table, initialize the root table info */
if (node->rootResultRelIndex >= 0)
+ {
mtstate->rootResultRelInfo = estate->es_root_result_relations +
node->rootResultRelIndex;
+ rootResultRel = mtstate->rootResultRelInfo;
+ }
+ else
+ rootResultRel = mtstate->resultRelInfo;
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2331,6 +2213,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
/*
+ * Build state for collecting transition tuples. This requires having a
+ * valid trigger query context, so skip it in explain-only mode.
+ */
+ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+ ExecSetupTransitionCaptureState(mtstate, estate);
+
+ /*
* call ExecInitNode on each of the plans to be executed and save the
* results into the array "mt_plans". This is also a convenient place to
* verify that the proposed target relations are valid and open their
@@ -2403,6 +2292,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
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. During INSERT, partition tuples to
+ * store into the transition tuple store are converted using
+ * PartitionToRoot map in the partition's PartitionRoutingInfo.
+ */
+ 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(rootResultRel->ri_RelationDesc));
resultRelInfo++;
i++;
}
@@ -2429,26 +2334,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupPartitionTupleRouting(estate, mtstate, rel);
/*
- * Build state for collecting transition tuples. This requires having a
- * valid trigger query context, so skip it in explain-only mode.
- */
- if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
- ExecSetupTransitionCaptureState(mtstate, estate);
-
- /*
- * Construct mapping from each of the per-subplan partition attnos to the
- * root attno. This is required when during update row movement the tuple
- * descriptor of a source partition does not match the root partitioned
- * table descriptor. In such a case we need to convert tuples to the root
- * tuple descriptor, because the search for destination partition starts
- * from the root. We'll also need a slot to store these converted tuples.
- * We can skip this setup if it's not a partition key update.
+ * 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)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
- }
/*
* Initialize any WITH CHECK OPTION constraints if needed.
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index a40ddf5..e38d732 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -46,7 +46,7 @@ typedef struct TriggerData
* The state for capturing old and new tuples into transition tables for a
* single ModifyTable node (or other operation source, e.g. copy.c).
*
- * This is per-caller to avoid conflicts in setting tcs_map or
+ * This is per-caller to avoid conflicts in setting
* tcs_original_insert_tuple. Note, however, that the pointed-to
* private data may be shared across multiple callers.
*/
@@ -66,14 +66,6 @@ typedef struct TransitionCaptureState
bool tcs_insert_new_table;
/*
- * For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the
- * new and old tuples from a child table's format to the format of the
- * relation named in a query so that it is compatible with the transition
- * tuplestores. The caller must store the conversion map here if so.
- */
- TupleConversionMap *tcs_map;
-
- /*
* For INSERT and COPY, it would be wasteful to convert tuples from child
* format to parent format after they have already been converted in the
* opposite direction during routing. In that case we bypass conversion
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cf832d7..647bb79 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -491,6 +491,14 @@ typedef struct ResultRelInfo
/* For use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
+
+ /*
+ * Map to convert child result relation tuples to the format of the
+ * table actually mentioned in the query (called "root"). Set only
+ * if either transition tuple capture or update partition row
+ * movement is active.
+ */
+ TupleConversionMap *ri_ChildToRootMap;
} ResultRelInfo;
/* ----------------
@@ -1192,9 +1200,6 @@ typedef struct ModifyTableState
/* controls transition table population for INSERT...ON CONFLICT UPDATE */
struct TransitionCaptureState *mt_oc_transition_capture;
-
- /* Per plan map for tuple conversion from child to root */
- TupleConversionMap **mt_per_subplan_tupconv_maps;
} ModifyTableState;
/* ----------------
--
1.8.3.1
v3-0004-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v3-0004-Initialize-result-relation-information-lazily.patchDownload
From dc54b6af2efd2b4a30a046ba3dac4a802c83bd8e Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v3 4/4] Initialize result relation information lazily
Currently, ResultRelInfo of all result relation appearing in
PlannedStmt.resultRelations, PlannedStmt.rootResultRelations are
initialized before execution begins. That can be wasteful as only
one or a handful of potentially many result relations appearing in
those lists (especially resultRelations) may actually have any rows
to update or delete.
This refactors ModifyTable code so as to delay creating ResultRelInfo
of a given result relation to when its subplan produces a tuple for
the first time, which in turn means that we don't make one for result
relations for which no tuples are found to be processed. This can
save some amount of work in the cases with many unpruned partitions
in the plan of which only one or handful need to be processed, such
as with generic plans. (Now, without support for runtime pruning of
UPDATE and DELETE subplans, initializing ResultRelInfos may not be
too much work when compared to initializing the subplans themselves,
but we may have runtime pruning in the future when we will not have
to worry about the overhead of initializing result rels.)
As part of this, any place that assumes that a given result relation
can be accessed by its index in ModifyTableState.resultRelInfo is
updated to instead get it using the new function ExecGetResultRelInfo
which checks if one exists and create one at the appropriate index if
not.
ModifyTableState gets a new field mt_done_rels that is a bitmapset of
RT indexes of result relations that have been fully processed due to
their subplans having exhausted tuples to process.
---
contrib/postgres_fdw/postgres_fdw.c | 6 +-
src/backend/commands/copy.c | 4 +-
src/backend/commands/explain.c | 40 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/executor/execMain.c | 123 ++---
src/backend/executor/execPartition.c | 111 ++--
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 850 ++++++++++++++++++-------------
src/backend/replication/logical/worker.c | 2 +-
src/include/executor/executor.h | 4 +
src/include/nodes/execnodes.h | 7 +-
11 files changed, 652 insertions(+), 501 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index fc53c23..abe55b7 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1943,7 +1943,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
if (plan && plan->operation == CMD_UPDATE &&
(resultRelInfo->ri_usesFdwDirectModify ||
resultRelInfo->ri_FdwState) &&
- resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan)
+ !list_member_int(mtstate->mt_done_rels, resultRelation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route tuples into foreign table to be updated \"%s\"",
@@ -1997,7 +1997,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
*/
if (plan && plan->operation == CMD_UPDATE &&
resultRelation == plan->rootRelation)
- resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+ resultRelation = linitial_int(plan->resultRelations);
}
/* Construct the SQL command string. */
@@ -2458,7 +2458,7 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = &estate->es_result_relations[dmstate->resultRelIndex];
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[dmstate->resultRelIndex];
/* The executor must have initialized the ResultRelInfo for us. */
Assert(resultRelInfo != NULL);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 155ac5b..739e526 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2838,7 +2838,7 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
@@ -2852,7 +2852,7 @@ CopyFrom(CopyState cstate)
mtstate->ps.plan = NULL;
mtstate->ps.state = estate;
mtstate->operation = CMD_INSERT;
- mtstate->resultRelInfo = estate->es_result_relations;
+ mtstate->resultRelInfo = resultRelInfo;
if (resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1e565fd..e7f3632 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -795,13 +796,21 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
show_relname = (numrels > 1 || numrootrels > 0 ||
routerels != NIL || targrels != NIL);
- rInfo = queryDesc->estate->es_result_relations;
- for (nr = 0; nr < numrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ for (nr = 0; nr < numrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_result_relations[nr];
- rInfo = queryDesc->estate->es_root_result_relations;
- for (nr = 0; nr < numrootrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
+
+ for (nr = 0; nr < numrootrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_root_result_relations[nr];
+
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
foreach(l, routerels)
{
@@ -3668,15 +3677,28 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ linitial_int(node->resultRelations) != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
- FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
+ /*
+ * Fetch ResultRelInfo to show target relation information. Some or
+ * all ResultRelInfos may not have been built either because
+ * ModifyTable is not executed at all (with ANALYZE off), or some were
+ * not processed during execution (with ANALYZE on). We ask to create
+ * any missing ResultRelInfos by passing true for 'create_it'.
+ */
+ ResultRelInfo *resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex + j, true);
+ FdwRoutine *fdwroutine;
+
+ if (resultRelInfo == NULL)
+ continue;
+
+ fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ac53f79..b5bbf3a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1789,7 +1789,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
0);
resultRelInfo++;
}
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations = &resultRelInfos;
estate->es_num_result_relations = list_length(rels);
/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..6d560c3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,35 +828,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_plannedstmt = plannedstmt;
/*
- * Initialize ResultRelInfo data structures, and open the result rels.
+ * Allocate space for ResultRelInfo pointers that will be filled later.
+ * See ExecGetResultRelInfo() and ExecGetRootResultRelInfo().
*/
if (plannedstmt->resultRelations)
{
List *resultRelations = plannedstmt->resultRelations;
int numResultRelations = list_length(resultRelations);
- ResultRelInfo *resultRelInfos;
- ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, resultRelations)
- {
- Index resultRelationIndex = lfirst_int(l);
- Relation resultRelation;
-
- resultRelation = ExecGetRangeTableRelation(estate,
- resultRelationIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelation,
- resultRelationIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
estate->es_num_result_relations = numResultRelations;
-
/* es_result_relation_info is NULL except when within ModifyTable */
estate->es_result_relation_info = NULL;
@@ -869,25 +851,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
{
int num_roots = list_length(plannedstmt->rootResultRelations);
- resultRelInfos = (ResultRelInfo *)
- palloc(num_roots * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, plannedstmt->rootResultRelations)
- {
- Index resultRelIndex = lfirst_int(l);
- Relation resultRelDesc;
-
- resultRelDesc = ExecGetRangeTableRelation(estate,
- resultRelIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelDesc,
- resultRelIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
-
- estate->es_root_result_relations = resultRelInfos;
+ estate->es_root_result_relations =
+ palloc0(num_roots * sizeof(ResultRelInfo *));
estate->es_num_root_result_relations = num_roots;
}
else
@@ -1377,24 +1342,18 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
MemoryContext oldcontext;
/* First, search through the query result relations */
- rInfo = estate->es_result_relations;
- nr = estate->es_num_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/* Second, search through the root result relations, if any */
- rInfo = estate->es_root_result_relations;
- nr = estate->es_num_root_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_root_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_root_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/*
@@ -1561,13 +1520,20 @@ ExecEndPlan(PlanState *planstate, EState *estate)
/*
* close indexes of result relation(s) if any. (Rels themselves get
- * closed next.)
+ * closed next.) Also, allow the FDWs to shut down.
*/
- resultRelInfo = estate->es_result_relations;
- for (i = estate->es_num_result_relations; i > 0; i--)
+ for (i = 0; i < estate->es_num_result_relations; i++)
{
- ExecCloseIndices(resultRelInfo);
- resultRelInfo++;
+ resultRelInfo = estate->es_result_relations[i];
+ if (resultRelInfo)
+ {
+ ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
+ }
}
/*
@@ -2796,23 +2762,42 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree)
{
int numResultRelations = parentestate->es_num_result_relations;
int numRootResultRels = parentestate->es_num_root_result_relations;
- ResultRelInfo *resultRelInfos;
+ int i;
+ ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_result_relations,
- numResultRelations * sizeof(ResultRelInfo));
- rcestate->es_result_relations = resultRelInfos;
+ rcestate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
+ for (i = 0; i < numResultRelations; i++)
+ {
+ if (parentestate->es_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_result_relations = numResultRelations;
/* Also transfer partitioned root result relations. */
if (numRootResultRels > 0)
{
- resultRelInfos = (ResultRelInfo *)
- palloc(numRootResultRels * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_root_result_relations,
- numRootResultRels * sizeof(ResultRelInfo));
- rcestate->es_root_result_relations = resultRelInfos;
+ rcestate->es_root_result_relations =
+ palloc0(numRootResultRels * sizeof(ResultRelInfo *));
+ for (i = 0; i < numRootResultRels; i++)
+ {
+ if (parentestate->es_root_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_root_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_root_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_root_result_relations = numRootResultRels;
}
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 7ee2dd9..cbc5e0e 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -151,7 +151,7 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
@@ -212,7 +212,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -234,18 +233,38 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
+ return proute;
+}
+
+/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
ExecHashSubPlanResultRelsByOid(mtstate, proute);
- return proute;
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ /*
+ * The UPDATE result relation may not have been processed and hence its
+ * ResultRelInfo not created yet, so pass true for 'create_it'.
+ */
+ if (elem)
+ return ExecGetResultRelInfo(mtstate,
+ node->resultRelIndex + elem->index,
+ true);
+ return NULL;
}
/*
@@ -352,7 +371,7 @@ ExecFindPartition(ModifyTableState *mtstate,
if (partdesc->is_leaf[partidx])
{
- ResultRelInfo *rri;
+ ResultRelInfo *rri = NULL;
/*
* Look to see if we've already got a ResultRelInfo for this
@@ -366,36 +385,31 @@ ExecFindPartition(ModifyTableState *mtstate,
}
else
{
- bool found = false;
-
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if it's also an UPDATE result relation, we might as
+ * well use the one UPDATE might use.
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
- {
- found = true;
- rri = elem->rri;
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
- /* Verify this ResultRelInfo allows INSERTs */
+ /* Verify this ResultRelInfo allows INSERTs */
+ if (rri)
+ {
CheckValidResultRel(rri, CMD_INSERT);
/* Set up the PartitionRoutingInfo for it */
+ rri->ri_PartitionRoot = proute->partition_root;
ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
rri, partidx);
}
}
- /* We need to create a new one. */
- if (!found)
+ /* Nope, We need to create a new one. */
+ if (rri == NULL)
rri = ExecInitPartitionInfo(mtstate, estate, proute,
dispatch,
rootResultRelInfo, partidx);
@@ -456,9 +470,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -469,26 +487,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -509,7 +527,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Index firstVarno = node ? linitial_int(node->resultRelations) : 0;
+ Relation firstResultRel = firstVarno > 0 ?
+ ExecGetRangeTableRelation(estate, firstVarno) : NULL;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -550,14 +570,13 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -621,7 +640,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -680,7 +698,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -931,8 +948,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
/*
* If the partition appears to be a reused UPDATE result relation, the
* necessary map would already have been set in ri_ChildToRootMap by
- * ExecInitModifyTable(), so use that one instead of building one from
- * scratch. One can tell if it's actually a reused UPDATE result
+ * ExecBuildResultRelInfo(), so use that one instead of building one
+ * from scratch. One can tell if it's actually a reused UPDATE result
* relation by looking at its ri_RangeTableIndex which must be
* different from the root RT index.
*/
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d0e65b8..a67c023 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -711,13 +711,13 @@ ExecCreateScanSlotFromOuterPlan(EState *estate,
bool
ExecRelationIsTargetRelation(EState *estate, Index scanrelid)
{
- ResultRelInfo *resultRelInfos;
+ ResultRelInfo **resultRelInfos;
int i;
resultRelInfos = estate->es_result_relations;
for (i = 0; i < estate->es_num_result_relations; i++)
{
- if (resultRelInfos[i].ri_RangeTableIndex == scanrelid)
+ if (resultRelInfos[i]->ri_RangeTableIndex == scanrelid)
return true;
}
return false;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f8f4254..655b3d8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,6 +72,9 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
+static ResultRelInfo *ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex);
+static ResultRelInfo *ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int globalRelIndex);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -359,6 +362,405 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
MemoryContextSwitchTo(oldContext);
}
+/*
+ * ExecGetResultRelInfo
+ * Returns the result relation at a given offset in es_result_relations
+ *
+ * If not present and 'create_it' is true, it is created and put at the given
+ * offset for subsequent calls to find.
+ *
+ * This allows lazy initialization of ResultRelInfos. That can be helpful in
+ * the case where there are multiple result relations due to inheritance but
+ * only one or few actually end up actually having any tuples to process.
+ *
+ * Note: only call from the executor proper or anything that possesses a valid
+ * execution context, that is an EState with a PlannedStmt, because this
+ * depends on finding a valid PlannedStmt to get result relation RT indexes
+ * from.
+ */
+ResultRelInfo *
+ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[resultRelIndex];
+
+ if (resultRelInfo == NULL && create_it)
+ {
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
+ Index rti = list_nth_int(resultRelations, resultRelIndex);
+
+ Assert(mtstate != NULL && mtstate->ps.plan != NULL);
+ resultRelInfo = ExecBuildResultRelInfo(mtstate, rti, resultRelIndex);
+ estate->es_result_relations[resultRelIndex] = resultRelInfo;
+ }
+
+ return resultRelInfo;
+}
+
+/*
+ * ExecGetRootResultRelInfo
+ * Like ExecGetResultRelInfo, but for "root" result relations
+ * corresponding to partitioned tables, which are managed separately from
+ * leaf result relations
+ *
+ * Root ResultRelInfos are never created lazily, although it seems better to
+ * have the same interface to avoid exposing ExecBuildResultRelInfo().
+ */
+static ResultRelInfo *
+ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *rootRelInfo = estate->es_root_result_relations[rootRelIndex];
+
+ if (rootRelInfo == NULL)
+ {
+ List *rootRelations = estate->es_plannedstmt->rootResultRelations;
+ Index rti = list_nth_int(rootRelations, rootRelIndex);
+
+ Assert(mtstate != NULL && mtstate->ps.plan != NULL);
+ rootRelInfo = ExecBuildResultRelInfo(mtstate, rti, rootRelIndex);
+ estate->es_root_result_relations[rootRelIndex] = rootRelInfo;
+ }
+
+ return rootRelInfo;
+}
+
+/*
+ * ExecBuildResultRelInfo
+ * Builds a ResultRelInfo for a result relation with given RT index
+ *
+ * Beside creating the ResultRelInfo and setting its various fields based on
+ * the provided ModifyTable plan, this may also set some fields in the
+ * ModifyTableState.
+ */
+static ResultRelInfo *
+ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int globalRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ CmdType operation = node->operation;
+ int firstRelIndex = node->resultRelIndex;
+ int thisRelIndex = globalRelIndex - firstRelIndex;
+ Plan *subplan = mtstate->mt_plans[thisRelIndex]->plan;
+ Relation relation = ExecGetRangeTableRelation(estate, rti);
+ ResultRelInfo *resultRelInfo;
+ bool update_tuple_routing_needed = false;
+ ListCell *l;
+ int eflags = estate->es_top_eflags;
+ bool junk_filter_needed = false;
+ MemoryContext oldcxt;
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, relation, rti, NULL,
+ estate->es_instrument);
+
+ /*
+ * Verify result relation is a valid target for the current operation
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /*
+ * If this is the root result relation of an UPDATE/DELETE, it only needs
+ * to look minimally valid.
+ */
+ if (rti == node->rootRelation)
+ {
+ MemoryContextSwitchTo(oldcxt);
+ return resultRelInfo;
+ }
+
+ /*
+ * 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);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(thisRelIndex,
+ node->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists,
+ thisRelIndex);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ thisRelIndex,
+ eflags);
+ }
+
+ /*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ if (node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists,
+ thisRelIndex);
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* RETURNING list */
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists,
+ thisRelIndex);
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (node->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (node->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a slot
+ * of the table's type here, because the slot will be used to insert
+ * into the table, and for RETURNING processing - which may access
+ * system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(node->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (node->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) node->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize the junk filter(s) if needed. INSERT queries need a filter
+ * if there are any junk attrs in the tlist. UPDATE and DELETE always
+ * need a filter, since there's always at least one junk attribute present
+ * --- no need to look first. Typically, this will be a 'ctid' or
+ * 'wholerow' attribute, but in the case of a foreign data wrapper it
+ * might be a set of junk attributes sufficient to identify the remote
+ * row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
+ * can't be fooled by some needing a filter and some not.
+ *
+ * This section of code is also a convenient place to verify that the
+ * output of an INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ j = ExecInitJunkFilter(subplan->targetlist, junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+ else if (operation == CMD_INSERT)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * For UPDATE on a partitioned tables, tuple routing might be needed if
+ * if the plan says so or a BEFORE UPDATE trigger is present on the
+ * partition which might modify the partition-key values.
+ */
+ if (mtstate->rootResultRelInfo && operation == CMD_UPDATE &&
+ (node->partColsUpdated ||
+ (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_update_before_row)))
+ update_tuple_routing_needed = true;
+
+ /*
+ * 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. During INSERT, partition tuples to
+ * store into the transition tuple store are converted using
+ * PartitionToRoot map in the partition's PartitionRoutingInfo.
+ */
+ if (update_tuple_routing_needed ||
+ (mtstate->mt_transition_capture && operation != CMD_INSERT))
+ {
+ Relation targetRel = getTargetResultRelInfo(mtstate)->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ }
+
+ /* ModifyTableState changes follow.*/
+
+ /* Result relation specific slot to store the plan's output tuple. */
+ mtstate->mt_scans[thisRelIndex] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(mtstate->mt_plans[thisRelIndex]),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* Tuple routing state may already have been initialized. */
+ if (update_tuple_routing_needed &&
+ mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rootRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return resultRelInfo;
+}
+
/* ----------------------------------------------------------------
* ExecInsert
*
@@ -1896,11 +2298,13 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
+ int firstRelIndex = plan->resultRelIndex;
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
EState *estate = node->ps.state;
CmdType operation = node->operation;
ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
JunkFilter *junkfilter;
TupleTableSlot *slot;
@@ -1943,9 +2347,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* es_result_relation_info must point to the currently active result
@@ -1956,8 +2358,6 @@ ExecModifyTable(PlanState *pstate)
*/
saved_resultRelInfo = estate->es_result_relation_info;
- estate->es_result_relation_info = resultRelInfo;
-
/*
* Fetch rows from subplan(s), and execute the required table modification
* for each row.
@@ -1980,18 +2380,50 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially creating it for the first time.
+ */
+ if (bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans) &&
+ resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelInfo(node,
+ firstRelIndex + node->mt_whichplan,
+ true);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
planSlot = ExecProcNode(subplanstate);
- if (TupIsNull(planSlot))
+ /*
+ * If we got a tuple to process and haven't initialized the
+ * ResultRelInfo to use for the subplan's target relation, fetch one
+ * if not not already done, potentially creating it for the first
+ * time.
+ */
+ if (!TupIsNull(planSlot) && resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelInfo(node,
+ firstRelIndex + node->mt_whichplan,
+ true);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+ else if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Mark the current result rel as having been fully processed. */
+ node->mt_done_rels = lappend_int(node->mt_done_rels,
+ list_nth_int(plan->resultRelations,
+ node->mt_whichplan));
+ resultRelInfo = NULL;
+ junkfilter = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
- estate->es_result_relation_info = resultRelInfo;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2167,14 +2599,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
Plan *subplan;
ListCell *l;
int i;
Relation rel;
- bool update_tuple_routing_needed = node->partColsUpdated;
- ResultRelInfo *rootResultRel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -2192,19 +2620,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_done = false;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
- mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
- /* If modifying a partitioned table, initialize the root table info */
- if (node->rootResultRelIndex >= 0)
- {
- mtstate->rootResultRelInfo = estate->es_root_result_relations +
- node->rootResultRelIndex;
- rootResultRel = mtstate->rootResultRelInfo;
- }
- else
- rootResultRel = mtstate->resultRelInfo;
-
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2213,197 +2630,33 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
/*
- * Build state for collecting transition tuples. This requires having a
- * valid trigger query context, so skip it in explain-only mode.
- */
- if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
- ExecSetupTransitionCaptureState(mtstate, estate);
-
- /*
* call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries. Note we *must* set
- * estate->es_result_relation_info correctly while we initialize each
- * sub-plan; external modules such as FDWs may depend on that (see
- * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one
- * example).
+ * results into the array "mt_plans".
*/
- saved_resultRelInfo = estate->es_result_relation_info;
-
- resultRelInfo = mtstate->resultRelInfo;
i = 0;
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
/* Now init the plan for this result rel */
- estate->es_result_relation_info = resultRelInfo;
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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. During INSERT, partition tuples to
- * store into the transition tuple store are converted using
- * PartitionToRoot map in the partition's PartitionRoutingInfo.
- */
- 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(rootResultRel->ri_RelationDesc));
- resultRelInfo++;
- i++;
- }
-
- estate->es_result_relation_info = saved_resultRelInfo;
-
- /* Get the target relation */
- rel = (getTargetResultRelInfo(mtstate))->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.
- */
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- (operation == CMD_INSERT || update_tuple_routing_needed))
- 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
}
- /*
- * Initialize RETURNING projections if needed.
- */
+ /* Initialize some global state for RETURNING projections. */
if (node->returningLists)
{
- TupleTableSlot *slot;
- ExprContext *econtext;
-
/*
* Initialize result tuple slot and assign its rowtype using the first
* RETURNING list. We assume the rest will look the same.
*/
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+ mtstate->ps.plan->targetlist = linitial(node->returningLists);
/* Set up a slot for the output of the RETURNING projection(s) */
ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
/* Need an econtext too */
if (mtstate->ps.ps_ExprContext == NULL)
ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
}
else
{
@@ -2417,67 +2670,51 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->ps.ps_ExprContext = NULL;
}
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
/*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
+ * Initialize the target relation for getTargetResultRelInfo() to return
+ * for this ModifyTableState. Other result relations, especially those
+ * for UPDATE and DELETE that have an RT index present in
+ * node->resultRelations, are initialized on as-needed basis during
+ * execution. INSERT or UPDATE tuple routing target partitions, which are
+ * not present in node->resultRelations, are also initialized on as-needed
+ * basis but managed by execPartition.c, not by nodeModifyTable.c.
+ *
+ * For UPDATE, DELETE on a partitioned table, the target relation is the
+ * "root" table given by node->rootResultRelIndex and
+ * node->resultRelations contains only leaf partitions. In all other cases,
+ * we initialize the first relation appearing in node->resultRelations as
+ * the target relation. For INSERTs, there's only one relation in
+ * node->resultRelations.
*/
- if (node->onConflictAction == ONCONFLICT_UPDATE)
+ if (node->rootResultRelIndex >= 0)
{
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ Assert(operation != CMD_INSERT);
+ mtstate->rootResultRelInfo =
+ ExecGetRootResultRelInfo(mtstate, node->rootResultRelIndex);
+ }
+ else
+ mtstate->resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex, true);
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
+ /* Get the target relation */
+ rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc;
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
+ /*
+ * Build state for tuple routing if it's an INSERT. If an UPDATE might
+ * need it, ExecBuildResultRelInfo will build it when initializing
+ * a partition's ResultRelInfo.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ operation == CMD_INSERT)
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rel);
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
+ /*
+ * Build state for collecting transition tuples. This requires having a
+ * valid trigger query context, so skip it in explain-only mode.
+ */
+ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+ ExecSetupTransitionCaptureState(mtstate, estate);
/*
* If we have any secondary relations in an UPDATE or DELETE, they need to
@@ -2515,109 +2752,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2647,20 +2781,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 2fcf2e6..689030b 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -213,7 +213,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
resultRelInfo = makeNode(ResultRelInfo);
InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 415e117..c65f0a8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -603,4 +603,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 647bb79..393aa49 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -530,7 +530,7 @@ typedef struct EState
CommandId es_output_cid;
/* Info about target table(s) for insert/update/delete queries: */
- ResultRelInfo *es_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_result_relations; /* array of ResultRelInfo pointers */
int es_num_result_relations; /* length of array */
ResultRelInfo *es_result_relation_info; /* currently active array elt */
@@ -540,7 +540,8 @@ typedef struct EState
* es_result_relations, but we need access to the roots for firing
* triggers and for runtime tuple routing.
*/
- ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_root_result_relations; /* array of ResultRelInfo
+ * pointers */
int es_num_root_result_relations; /* length of the array */
PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */
@@ -1177,6 +1178,8 @@ typedef struct ModifyTableState
PlanState **mt_plans; /* subplans (one per target rel) */
int mt_nplans; /* number of plans in the array */
int mt_whichplan; /* which one is being executed (0..n-1) */
+ List *mt_done_rels; /* RT indexes of result relations that have
+ * been fully processed. */
TupleTableSlot **mt_scans; /* input tuple corresponding to underlying
* plans */
ResultRelInfo *resultRelInfo; /* per-subplan target relations */
--
1.8.3.1
v3-0001-Revise-how-FDWs-obtain-result-relation-informatio.patchapplication/octet-stream; name=v3-0001-Revise-how-FDWs-obtain-result-relation-informatio.patchDownload
From 7342b1e3e5226d09808c8215db44a91f987d8ef6 Mon Sep 17 00:00:00 2001
From: Etsuro Fujita <efujita@postgresql.org>
Date: Thu, 8 Aug 2019 21:41:12 +0900
Subject: [PATCH v3 1/4] Revise how FDWs obtain result relation information
For BeginDirectModify, which is called when ExecInitModifyTable
initializes source plans, the only way currently to access information
about the foreign table being modified is through ResultRelInfo for
that table passed by setting EState.es_result_relation_info.
This commit installs a new field in ForeignScan node to pass
this information directly instead of through ResultRelInfo.
This provides two benefits:
1. Reduce the reliance on es_result_relation_info (a query-global
variable) being set correctly by the calling code, which can be
bug-prone especially in the case of an inherited update.
2. Allows ResultRelInfos to be created at a later stage of ModifyTable
execution instead of during ExecInitModifyTable.
Amit Langote, Etsuro Fujita
---
contrib/postgres_fdw/postgres_fdw.c | 26 ++++++++++++++++++--------
doc/src/sgml/fdwhandler.sgml | 10 ++++++----
src/backend/executor/nodeForeignscan.c | 5 ++++-
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 8 ++++++++
src/backend/optimizer/plan/setrefs.c | 15 +++++++++++++++
src/include/nodes/plannodes.h | 3 +++
9 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9fc53ca..fc53c23 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -200,6 +200,9 @@ typedef struct PgFdwDirectModifyState
Relation rel; /* relcache entry for the foreign table */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
+ int resultRelIndex; /* index of ResultRelInfo for the foreign table
+ * in EState.es_result_relations */
+
/* extracted fdw_private data */
char *query; /* text of UPDATE/DELETE command */
bool has_returning; /* is there a RETURNING clause? */
@@ -446,11 +449,12 @@ static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
static void execute_dml_stmt(ForeignScanState *node);
-static TupleTableSlot *get_returning_data(ForeignScanState *node);
+static TupleTableSlot *get_returning_data(ForeignScanState *node, ResultRelInfo *resultRelInfo);
static void init_returning_filter(PgFdwDirectModifyState *dmstate,
List *fdw_scan_tlist,
Index rtindex);
static TupleTableSlot *apply_returning_filter(PgFdwDirectModifyState *dmstate,
+ ResultRelInfo *relInfo,
TupleTableSlot *slot,
EState *estate);
static void prepare_query_params(PlanState *node,
@@ -2332,6 +2336,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
{
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
PgFdwDirectModifyState *dmstate;
Index rtindex;
RangeTblEntry *rte;
@@ -2356,7 +2361,9 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does.
*/
- rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
+ Assert(fsplan->resultRelIndex >= 0);
+ dmstate->resultRelIndex = fsplan->resultRelIndex;
+ rtindex = list_nth_int(resultRelations, fsplan->resultRelIndex);
rte = exec_rt_fetch(rtindex, estate);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
@@ -2451,7 +2458,10 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = &estate->es_result_relations[dmstate->resultRelIndex];
+
+ /* The executor must have initialized the ResultRelInfo for us. */
+ Assert(resultRelInfo != NULL);
/*
* If this is the first call after Begin, execute the statement.
@@ -2483,7 +2493,7 @@ postgresIterateDirectModify(ForeignScanState *node)
/*
* Get the next RETURNING tuple.
*/
- return get_returning_data(node);
+ return get_returning_data(node, resultRelInfo);
}
/*
@@ -4083,11 +4093,10 @@ execute_dml_stmt(ForeignScanState *node)
* Get the result of a RETURNING clause.
*/
static TupleTableSlot *
-get_returning_data(ForeignScanState *node)
+get_returning_data(ForeignScanState *node, ResultRelInfo *resultRelInfo)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
TupleTableSlot *resultSlot;
@@ -4142,7 +4151,8 @@ get_returning_data(ForeignScanState *node)
if (dmstate->rel)
resultSlot = slot;
else
- resultSlot = apply_returning_filter(dmstate, slot, estate);
+ resultSlot = apply_returning_filter(dmstate, resultRelInfo, slot,
+ estate);
}
dmstate->next_tuple++;
@@ -4231,10 +4241,10 @@ init_returning_filter(PgFdwDirectModifyState *dmstate,
*/
static TupleTableSlot *
apply_returning_filter(PgFdwDirectModifyState *dmstate,
+ ResultRelInfo *relInfo,
TupleTableSlot *slot,
EState *estate)
{
- ResultRelInfo *relInfo = estate->es_result_relation_info;
TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
TupleTableSlot *resultSlot;
Datum *values;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 7479303..54154ee 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -892,8 +892,9 @@ BeginDirectModify(ForeignScanState *node,
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
<structname>ForeignScanState</structname> node (in particular, from the underlying
- <structname>ForeignScan</structname> plan node, which contains any FDW-private
- information provided by <function>PlanDirectModify</function>).
+ <structname>ForeignScan</structname> plan node, which contains an integer field
+ giving the table's index in the query's list of result relations along with any
+ FDW-private information provided by <function>PlanDirectModify</function>.
<literal>eflags</literal> contains flag bits describing the executor's
operating mode for this plan node.
</para>
@@ -925,8 +926,9 @@ IterateDirectModify(ForeignScanState *node);
tuple table slot (the node's <structfield>ScanTupleSlot</structfield> should be
used for this purpose). The data that was actually inserted, updated
or deleted must be stored in the
- <literal>es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
- of the node's <structname>EState</structname>.
+ <literal>ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
+ of the target foreign table's <structname>ResultRelInfo</structname>
+ obtained using the information passed to <function>BeginDirectModify</function>.
Return NULL if no more rows are available.
Note that this is called in a short-lived memory context that will be
reset between invocations. Create a memory context in
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 513471a..19433b3 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -221,10 +221,13 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan or the direct modification.
*/
if (node->operation != CMD_SELECT)
+ {
+ Assert(node->resultRelIndex >= 0);
fdwroutine->BeginDirectModify(scanstate, eflags);
+ }
else
fdwroutine->BeginForeignScan(scanstate, eflags);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409d..2afb195 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -761,6 +761,7 @@ _copyForeignScan(const ForeignScan *from)
COPY_NODE_FIELD(fdw_recheck_quals);
COPY_BITMAPSET_FIELD(fs_relids);
COPY_SCALAR_FIELD(fsSystemCol);
+ COPY_SCALAR_FIELD(resultRelIndex);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f1775..15fd85a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -698,6 +698,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
WRITE_NODE_FIELD(fdw_recheck_quals);
WRITE_BITMAPSET_FIELD(fs_relids);
WRITE_BOOL_FIELD(fsSystemCol);
+ WRITE_INT_FIELD(resultRelIndex);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab..4024a80 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2017,6 +2017,7 @@ _readForeignScan(void)
READ_NODE_FIELD(fdw_recheck_quals);
READ_BITMAPSET_FIELD(fs_relids);
READ_BOOL_FIELD(fsSystemCol);
+ READ_INT_FIELD(resultRelIndex);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 99278ee..4e86249 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -5541,6 +5541,8 @@ make_foreignscan(List *qptlist,
node->fs_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;
+ /* resultRelIndex will be set by make_modifytable(), if needed */
+ node->resultRelIndex = -1;
return node;
}
@@ -6900,7 +6902,13 @@ make_modifytable(PlannerInfo *root,
!has_stored_generated_columns(subroot, rti))
direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
if (direct_modify)
+ {
+ ForeignScan *fscan = (ForeignScan *) list_nth(subplans, i);
+
+ Assert(IsA(fscan, ForeignScan));
+ fscan->resultRelIndex = i;
direct_modify_plans = bms_add_member(direct_modify_plans, i);
+ }
if (!direct_modify &&
fdwroutine != NULL &&
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index baefe0e..f3d1a12 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -904,6 +904,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
rc->rti += rtoffset;
rc->prti += rtoffset;
}
+ /*
+ * Caution: Do not change the relative ordering of this loop
+ * and the statement below that adds the result relations to
+ * root->glob->resultRelations, because we need to use the
+ * current value of list_length(root->glob->resultRelations)
+ * in some plans.
+ */
foreach(l, splan->plans)
{
lfirst(l) = set_plan_refs(root,
@@ -1243,6 +1250,14 @@ set_foreignscan_references(PlannerInfo *root,
}
fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
+
+ /*
+ * Adjust resultRelIndex if it's valid (note that we are called before
+ * adding the RT indexes of ModifyTable result relations to the global
+ * list)
+ */
+ if (fscan->resultRelIndex >= 0)
+ fscan->resultRelIndex += list_length(root->glob->resultRelations);
}
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 83e0107..7314d2f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -620,6 +620,9 @@ typedef struct ForeignScan
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
Bitmapset *fs_relids; /* RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
+ int resultRelIndex; /* index of foreign table in the list of query
+ * result relations for INSERT/UPDATE/DELETE;
+ * -1 for SELECT */
} ForeignScan;
/* ----------------
--
1.8.3.1
v3-0002-Don-t-make-root-ResultRelInfo-for-insert-queries.patchapplication/octet-stream; name=v3-0002-Don-t-make-root-ResultRelInfo-for-insert-queries.patchDownload
From 2ff8683dff093f81fb0913312bfeca6c8d02de56 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 13:12:21 +0900
Subject: [PATCH v3 2/4] Don't make "root" ResultRelInfo for insert queries
For inserts on partitioned tables, we don't need a separate
ResultRelInfo for the root partitioned table, which being the query's
main target relation already has one, unlike UPDATE and DELETE where
only the leaf partitions are in the list of target relations.
We need a ResultRelInfo for the root partitioned table in the UPDATE
and DELETE cases so as to fire statement triggers on them. Also, in
the UPDATE's case it is used as the target result relation when
moving a row from one row to another.
---
src/backend/executor/execPartition.c | 2 +-
src/backend/executor/nodeModifyTable.c | 19 ++++++-------------
src/backend/optimizer/plan/planner.c | 12 +-----------
src/backend/optimizer/plan/setrefs.c | 11 ++++++++---
src/include/nodes/pathnodes.h | 4 +++-
src/include/nodes/plannodes.h | 8 ++++++--
6 files changed, 25 insertions(+), 31 deletions(-)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..ef17acd 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -522,7 +522,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
leaf_part_rri = makeNode(ResultRelInfo);
InitResultRelInfo(leaf_part_rri,
partrel,
- node ? node->rootRelation : 1,
+ rootResultRelInfo->ri_RangeTableIndex,
rootrel,
estate->es_instrument);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..09a9871 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1738,15 +1738,7 @@ static void
fireBSTriggers(ModifyTableState *node)
{
ModifyTable *plan = (ModifyTable *) node->ps.plan;
- ResultRelInfo *resultRelInfo = node->resultRelInfo;
-
- /*
- * If the node modifies a partitioned table, we must fire its triggers.
- * Note that in that case, node->resultRelInfo points to the first leaf
- * partition, not the root table.
- */
- if (node->rootResultRelInfo != NULL)
- resultRelInfo = node->rootResultRelInfo;
+ ResultRelInfo *resultRelInfo = getTargetResultRelInfo(node);
switch (node->operation)
{
@@ -1772,17 +1764,18 @@ fireBSTriggers(ModifyTableState *node)
* Return the target rel ResultRelInfo.
*
* This relation is the same as :
- * - the relation for which we will fire AFTER STATEMENT triggers.
+ * - the relation for which we will fire BEFIRE/AFTER STATEMENT triggers.
* - the relation into whose tuple format all captured transition tuples must
* be converted.
- * - the root partitioned table.
+ * - the root partitioned table mentioned in an UPDATE or DELETE query.
*/
static ResultRelInfo *
getTargetResultRelInfo(ModifyTableState *node)
{
/*
- * Note that if the node modifies a partitioned table, node->resultRelInfo
- * points to the first leaf partition, not the root table.
+ * Note that if the node performs an UPDATE or DELETE on a partitioned
+ * table, node->resultRelInfo points to the first leaf partition, not the
+ * root table.
*/
if (node->rootResultRelInfo != NULL)
return node->rootResultRelInfo;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b40a112..9486873 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2329,22 +2329,12 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
*/
if (parse->commandType != CMD_SELECT && !inheritance_update)
{
- Index rootRelation;
+ Index rootRelation = 0;
List *withCheckOptionLists;
List *returningLists;
List *rowMarks;
/*
- * If target is a partition root table, we need to mark the
- * ModifyTable node appropriately for that.
- */
- if (rt_fetch(parse->resultRelation, parse->rtable)->relkind ==
- RELKIND_PARTITIONED_TABLE)
- rootRelation = parse->resultRelation;
- else
- rootRelation = 0;
-
- /*
* Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
* needed.
*/
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index f3d1a12..05a0882 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -930,12 +930,17 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->resultRelations);
/*
- * If the main target relation is a partitioned table, also
- * add the partition root's RT index to rootResultRelations,
- * and remember its index in that list in rootResultRelIndex.
+ * If the main target relation of an inherited UPDATE/DELETE
+ * operation is a partitioned table, also add the partition
+ * root's RT index to rootResultRelations, and remember its
+ * index in that list in rootResultRelIndex. We don't need
+ * this for INSERT though as there are no other result
+ * relations present in query beside the partition root whose
+ * index is given by resultRelIndex.
*/
if (splan->rootRelation)
{
+ Assert(splan->operation != CMD_INSERT);
splan->rootResultRelIndex =
list_length(root->glob->rootResultRelations);
root->glob->rootResultRelations =
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 485d1b0..2a2db9c 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1813,7 +1813,9 @@ typedef struct ModifyTablePath
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
- Index rootRelation; /* Root RT index, if target is partitioned */
+ Index rootRelation; /* RT index of root partitioned target
+ * relation; valid only for UPDATE or DELETE,
+ * 0 for INSERT */
bool partColsUpdated; /* some part key in hierarchy updated */
List *resultRelations; /* integer list of RT indexes */
List *subpaths; /* Path(s) producing source data */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7314d2f..13043d1 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -221,11 +221,15 @@ typedef struct ModifyTable
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
- Index rootRelation; /* Root RT index, if target is partitioned */
+ Index rootRelation; /* RT index of root partitioned target
+ * relation; valid only for UPDATE or DELETE,
+ * 0 for INSERT */
bool partColsUpdated; /* some part key in hierarchy updated */
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in plan's list */
- int rootResultRelIndex; /* index of the partitioned table root */
+ int rootResultRelIndex; /* index of root partitioned target
+ * relation in plan's list; valid only for
+ * UPDATE or DELETE, -1 for INSERT */
List *plans; /* plan(s) producing source data */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
--
1.8.3.1
On Tue, Aug 4, 2020 at 3:15 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Sat, Aug 1, 2020 at 4:46 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jun 26, 2020 at 8:36 AM Amit Langote <amitlangote09@gmail.com> wrote:
0001 and 0002 are preparatory patches.
I read through these patches a bit but it's really unclear what the
point of them is. I think they need better commit messages, or better
comments, or both.Thanks for taking a look. Sorry about the lack of good commentary,
which I have tried to address in the attached updated version. I
extracted one more part as preparatory from the earlier 0003 patch, so
there are 4 patches now.Also as discussed with Daniel, I have changed the patches so that they
can be applied on plain HEAD instead of having to first apply the
patches at [1]. Without runtime pruning for UPDATE/DELETE proposed in
[1], optimizing ResultRelInfo creation by itself does not improve the
performance/scalability by that much, but the benefit of lazily
creating ResultRelInfos seems clear so I think maybe it's okay to
pursue this independently.
Per cfbot's automatic patch tester, there were some issues in the 0004 patch:
nodeModifyTable.c: In function ‘ExecModifyTable’:
1529nodeModifyTable.c:2484:24: error: ‘junkfilter’ may be used
uninitialized in this function [-Werror=maybe-uninitialized]
1530 junkfilter->jf_junkAttNo,
1531 ^
1532nodeModifyTable.c:2309:14: note: ‘junkfilter’ was declared here
1533 JunkFilter *junkfilter;
1534 ^
1535cc1: all warnings being treated as errors
1536<builtin>: recipe for target 'nodeModifyTable.o' failed
1537make[3]: *** [nodeModifyTable.o] Error 1
Fixed in the attached updated version
--
Amit Langote
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v4-0003-Revise-child-to-root-tuple-conversion-map-managem.patchapplication/octet-stream; name=v4-0003-Revise-child-to-root-tuple-conversion-map-managem.patchDownload
From fa8b918516f9151e105a2ad0dc0e2f16705a3eff Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 30 Jul 2019 10:51:35 +0900
Subject: [PATCH v4 3/4] Revise child-to-root tuple conversion map management
Transition tuple capture requires to convert child tuples to the
inheritance root table format because that's the format the
transition tuplestore stores tuple in. For INSERTs into partitioned
tables, the conversion is handled by tuple routing code which
constructs the map for a given partition only if the partition is
targeted, but for UPDATE and DELETE, maps for all result relations
are made and stored in an array in ModifyTableState during
ExecInitModifyTable, which requires their ResultRelInfos to have been
already built. During execution, map for the currently active result
relation is set in TransitionCaptureState.tcs_map.
This commit removes TransitionCaptureMap.tcs_map in favor a new
map field in ResultRelInfo named ri_ChildToRootMap that is
initialized when the ResultRelInfo for a given result relation is.
This way is less confusing and less bug-prone than setting and
resetting tcs_map. Also, this will also allow us to delay creating
the map for a given result relation to when that relation is actually
processed during execution.
---
src/backend/commands/copy.c | 30 +----
src/backend/commands/trigger.c | 9 +-
src/backend/executor/execPartition.c | 20 +++-
src/backend/executor/nodeModifyTable.c | 203 ++++++++-------------------------
src/include/commands/trigger.h | 10 +-
src/include/nodes/execnodes.h | 11 +-
6 files changed, 85 insertions(+), 198 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db7d24a..155ac5b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3117,32 +3117,14 @@ CopyFrom(CopyState cstate)
estate->es_result_relation_info = resultRelInfo;
/*
- * If we're capturing transition tuples, we might need to convert
- * from the partition rowtype to root rowtype.
+ * If we're capturing transition tuples and there are no BEFORE
+ * triggers on the partition which may change the tuple, we can
+ * just remember the original unconverted tuple to avoid a
+ * needless round trip conversion.
*/
if (cstate->transition_capture != NULL)
- {
- if (has_before_insert_row_trig)
- {
- /*
- * If there are any BEFORE triggers on the partition,
- * we'll have to be ready to convert their result back to
- * tuplestore format.
- */
- cstate->transition_capture->tcs_original_insert_tuple = NULL;
- cstate->transition_capture->tcs_map =
- resultRelInfo->ri_PartitionInfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted
- * tuple, to avoid a needless round trip conversion.
- */
- cstate->transition_capture->tcs_original_insert_tuple = myslot;
- cstate->transition_capture->tcs_map = NULL;
- }
- }
+ cstate->transition_capture->tcs_original_insert_tuple =
+ !has_before_insert_row_trig ? myslot : NULL;
/*
* We might need to convert from the root rowtype to the partition
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..d1b5a03 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -35,6 +35,7 @@
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
+#include "executor/execPartition.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
@@ -4293,8 +4294,8 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
* tables, then return NULL.
*
* The resulting object can be passed to the ExecAR* functions. The caller
- * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing
- * with child tables.
+ * should set tcs_original_insert_tuple as appropriate when dealing with child
+ * tables
*
* Note that we copy the flags from a parent table into this struct (rather
* than subsequently using the relation's TriggerDesc directly) so that we can
@@ -5389,7 +5390,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = transition_capture->tcs_map;
+ PartitionRoutingInfo *pinfo = relinfo->ri_PartitionInfo;
+ TupleConversionMap *map = pinfo ? pinfo->pi_PartitionToRootMap :
+ relinfo->ri_ChildToRootMap;
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fe02238..2ab41f1 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -926,9 +926,23 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
if (mtstate &&
(mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
{
- partrouteinfo->pi_PartitionToRootMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
- RelationGetDescr(partRelInfo->ri_PartitionRoot));
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+
+ /*
+ * If the partition appears to be a reused UPDATE result relation, the
+ * necessary map would already have been set in ri_ChildToRootMap by
+ * ExecInitModifyTable(), so use that one instead of building one from
+ * scratch. One can tell if it's actually a reused UPDATE result
+ * relation by looking at its ri_RangeTableIndex which must be
+ * different from the root RT index.
+ */
+ if (node && node->operation == CMD_UPDATE &&
+ node->rootRelation != partRelInfo->ri_RangeTableIndex)
+ partrouteinfo->pi_PartitionToRootMap = partRelInfo->ri_ChildToRootMap;
+ else
+ partrouteinfo->pi_PartitionToRootMap =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
+ RelationGetDescr(partRelInfo->ri_PartitionRoot));
}
else
partrouteinfo->pi_PartitionToRootMap = NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 09a9871..f8f4254 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,9 +72,6 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
-static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
-static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
- int whichplan);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1080,7 +1077,6 @@ ExecUpdate(ModifyTableState *mtstate,
TM_Result result;
TM_FailureData tmfd;
List *recheckIndexes = NIL;
- TupleConversionMap *saved_tcs_map = NULL;
/*
* abort the operation if not running transactions
@@ -1205,7 +1201,6 @@ lreplace:;
TupleTableSlot *ret_slot;
TupleTableSlot *epqslot = NULL;
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
- int map_index;
TupleConversionMap *tupconv_map;
/*
@@ -1275,26 +1270,11 @@ lreplace:;
}
/*
- * Updates set the transition capture map only when a new subplan
- * is chosen. But for inserts, it is set for each row. So after
- * INSERT, we need to revert back to the map created for UPDATE;
- * otherwise the next UPDATE will incorrectly use the one created
- * for INSERT. So first save the one created for UPDATE.
- */
- if (mtstate->mt_transition_capture)
- saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
-
- /*
* resultRelInfo is one of the per-subplan resultRelInfos. So we
* should convert the tuple into root's tuple descriptor, since
- * ExecInsert() starts the search from root. The tuple conversion
- * map list is in the order of mtstate->resultRelInfo[], so to
- * retrieve the one for this resultRel, we need to know the
- * position of the resultRel in mtstate->resultRelInfo[].
+ * ExecInsert() starts the search from root.
*/
- map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
- tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
+ tupconv_map = resultRelInfo->ri_ChildToRootMap;
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1313,11 +1293,13 @@ lreplace:;
/* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
+
+ /*
+ * Reset the transition state that may possibly have been written
+ * by INSERT.
+ */
if (mtstate->mt_transition_capture)
- {
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map = saved_tcs_map;
- }
return ret_slot;
}
@@ -1837,28 +1819,6 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
RelationGetRelid(targetRelInfo->ri_RelationDesc),
CMD_UPDATE);
-
- /*
- * If we found that we need to collect transition tuples then we may also
- * need tuple conversion maps for any children that have TupleDescs that
- * aren't compatible with the tuplestores. (We can share these maps
- * between the regular and ON CONFLICT cases.)
- */
- if (mtstate->mt_transition_capture != NULL ||
- mtstate->mt_oc_transition_capture != NULL)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
-
- /*
- * Install the conversion map for the first plan for UPDATE and DELETE
- * operations. It will be advanced each time we switch to the next
- * plan. (INSERT operations set it every time, so we need not update
- * mtstate->mt_oc_transition_capture here.)
- */
- if (mtstate->mt_transition_capture && mtstate->operation != CMD_INSERT)
- mtstate->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(mtstate, 0);
- }
}
/*
@@ -1882,6 +1842,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *partrel;
PartitionRoutingInfo *partrouteinfo;
TupleConversionMap *map;
+ bool has_before_insert_row_trig;
/*
* Lookup the target partition's ResultRelInfo. If ExecFindPartition does
@@ -1900,37 +1861,15 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
estate->es_result_relation_info = partrel;
/*
- * If we're capturing transition tuples, we might need to convert from the
- * partition rowtype to root partitioned table's rowtype.
+ * If we're capturing transition tuples and there are no BEFORE triggers
+ * on the partition which may change the tuple, we can just remember the
+ * original unconverted tuple to avoid a needless round trip conversion.
*/
+ has_before_insert_row_trig = (partrel->ri_TrigDesc &&
+ partrel->ri_TrigDesc->trig_insert_before_row);
if (mtstate->mt_transition_capture != NULL)
- {
- if (partrel->ri_TrigDesc &&
- partrel->ri_TrigDesc->trig_insert_before_row)
- {
- /*
- * If there are any BEFORE triggers on the partition, we'll have
- * to be ready to convert their result back to tuplestore format.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted tuple, to
- * avoid a needless round trip conversion.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
- mtstate->mt_transition_capture->tcs_map = NULL;
- }
- }
- if (mtstate->mt_oc_transition_capture != NULL)
- {
- mtstate->mt_oc_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
+ mtstate->mt_transition_capture->tcs_original_insert_tuple =
+ !has_before_insert_row_trig ? slot : NULL;
/*
* Convert the tuple, if necessary.
@@ -1946,58 +1885,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
return slot;
}
-/*
- * Initialize the child-to-root tuple conversion map array for UPDATE subplans.
- *
- * This map array is required to convert the tuple from the subplan result rel
- * to the target table descriptor. This requirement arises for two independent
- * scenarios:
- * 1. For update-tuple-routing.
- * 2. For capturing tuples in transition tables.
- */
-static void
-ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
-{
- ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
- ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
- TupleDesc outdesc;
- int numResultRelInfos = mtstate->mt_nplans;
- int i;
-
- /*
- * Build array of conversion maps from each child's TupleDesc to the one
- * used in the target relation. The map pointers may be NULL when no
- * conversion is necessary, which is hopefully a common case.
- */
-
- /* Get tuple descriptor of the target rel. */
- outdesc = RelationGetDescr(targetRelInfo->ri_RelationDesc);
-
- mtstate->mt_per_subplan_tupconv_maps = (TupleConversionMap **)
- palloc(sizeof(TupleConversionMap *) * numResultRelInfos);
-
- for (i = 0; i < numResultRelInfos; ++i)
- {
- mtstate->mt_per_subplan_tupconv_maps[i] =
- convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
- outdesc);
- }
-}
-
-/*
- * For a given subplan index, get the tuple conversion map.
- */
-static TupleConversionMap *
-tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
-{
- /* If nobody else set the per-subplan array of maps, do so ourselves. */
- if (mtstate->mt_per_subplan_tupconv_maps == NULL)
- ExecSetupChildParentMapForSubplan(mtstate);
-
- Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
- return mtstate->mt_per_subplan_tupconv_maps[whichplan];
-}
-
/* ----------------------------------------------------------------
* ExecModifyTable
*
@@ -2107,17 +1994,6 @@ ExecModifyTable(PlanState *pstate)
estate->es_result_relation_info = resultRelInfo;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
- /* Prepare to convert transition tuples from this child. */
- if (node->mt_transition_capture != NULL)
- {
- node->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichplan);
- }
- if (node->mt_oc_transition_capture != NULL)
- {
- node->mt_oc_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichplan);
- }
continue;
}
else
@@ -2298,6 +2174,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
int i;
Relation rel;
bool update_tuple_routing_needed = node->partColsUpdated;
+ ResultRelInfo *rootResultRel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -2320,8 +2197,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
/* If modifying a partitioned table, initialize the root table info */
if (node->rootResultRelIndex >= 0)
+ {
mtstate->rootResultRelInfo = estate->es_root_result_relations +
node->rootResultRelIndex;
+ rootResultRel = mtstate->rootResultRelInfo;
+ }
+ else
+ rootResultRel = mtstate->resultRelInfo;
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2331,6 +2213,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
/*
+ * Build state for collecting transition tuples. This requires having a
+ * valid trigger query context, so skip it in explain-only mode.
+ */
+ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+ ExecSetupTransitionCaptureState(mtstate, estate);
+
+ /*
* call ExecInitNode on each of the plans to be executed and save the
* results into the array "mt_plans". This is also a convenient place to
* verify that the proposed target relations are valid and open their
@@ -2403,6 +2292,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
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. During INSERT, partition tuples to
+ * store into the transition tuple store are converted using
+ * PartitionToRoot map in the partition's PartitionRoutingInfo.
+ */
+ 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(rootResultRel->ri_RelationDesc));
resultRelInfo++;
i++;
}
@@ -2429,26 +2334,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupPartitionTupleRouting(estate, mtstate, rel);
/*
- * Build state for collecting transition tuples. This requires having a
- * valid trigger query context, so skip it in explain-only mode.
- */
- if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
- ExecSetupTransitionCaptureState(mtstate, estate);
-
- /*
- * Construct mapping from each of the per-subplan partition attnos to the
- * root attno. This is required when during update row movement the tuple
- * descriptor of a source partition does not match the root partitioned
- * table descriptor. In such a case we need to convert tuples to the root
- * tuple descriptor, because the search for destination partition starts
- * from the root. We'll also need a slot to store these converted tuples.
- * We can skip this setup if it's not a partition key update.
+ * 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)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
- }
/*
* Initialize any WITH CHECK OPTION constraints if needed.
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index a40ddf5..e38d732 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -46,7 +46,7 @@ typedef struct TriggerData
* The state for capturing old and new tuples into transition tables for a
* single ModifyTable node (or other operation source, e.g. copy.c).
*
- * This is per-caller to avoid conflicts in setting tcs_map or
+ * This is per-caller to avoid conflicts in setting
* tcs_original_insert_tuple. Note, however, that the pointed-to
* private data may be shared across multiple callers.
*/
@@ -66,14 +66,6 @@ typedef struct TransitionCaptureState
bool tcs_insert_new_table;
/*
- * For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the
- * new and old tuples from a child table's format to the format of the
- * relation named in a query so that it is compatible with the transition
- * tuplestores. The caller must store the conversion map here if so.
- */
- TupleConversionMap *tcs_map;
-
- /*
* For INSERT and COPY, it would be wasteful to convert tuples from child
* format to parent format after they have already been converted in the
* opposite direction during routing. In that case we bypass conversion
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cf832d7..647bb79 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -491,6 +491,14 @@ typedef struct ResultRelInfo
/* For use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
+
+ /*
+ * Map to convert child result relation tuples to the format of the
+ * table actually mentioned in the query (called "root"). Set only
+ * if either transition tuple capture or update partition row
+ * movement is active.
+ */
+ TupleConversionMap *ri_ChildToRootMap;
} ResultRelInfo;
/* ----------------
@@ -1192,9 +1200,6 @@ typedef struct ModifyTableState
/* controls transition table population for INSERT...ON CONFLICT UPDATE */
struct TransitionCaptureState *mt_oc_transition_capture;
-
- /* Per plan map for tuple conversion from child to root */
- TupleConversionMap **mt_per_subplan_tupconv_maps;
} ModifyTableState;
/* ----------------
--
1.8.3.1
v4-0002-Don-t-make-root-ResultRelInfo-for-insert-queries.patchapplication/octet-stream; name=v4-0002-Don-t-make-root-ResultRelInfo-for-insert-queries.patchDownload
From b682f86643176dbfe84a2323732ca37f51df04c6 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 13:12:21 +0900
Subject: [PATCH v4 2/4] Don't make "root" ResultRelInfo for insert queries
For inserts on partitioned tables, we don't need a separate
ResultRelInfo for the root partitioned table, which being the query's
main target relation already has one, unlike UPDATE and DELETE where
only the leaf partitions are in the list of target relations.
We need a ResultRelInfo for the root partitioned table in the UPDATE
and DELETE cases so as to fire statement triggers on them. Also, in
the UPDATE's case it is used as the target result relation when
moving a row from one row to another.
---
src/backend/executor/execPartition.c | 2 +-
src/backend/executor/nodeModifyTable.c | 19 ++++++-------------
src/backend/optimizer/plan/planner.c | 12 +-----------
src/backend/optimizer/plan/setrefs.c | 11 ++++++++---
src/include/nodes/pathnodes.h | 4 +++-
src/include/nodes/plannodes.h | 8 ++++++--
6 files changed, 25 insertions(+), 31 deletions(-)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 79fcbd6..fe02238 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -522,7 +522,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
leaf_part_rri = makeNode(ResultRelInfo);
InitResultRelInfo(leaf_part_rri,
partrel,
- node ? node->rootRelation : 1,
+ rootResultRelInfo->ri_RangeTableIndex,
rootrel,
estate->es_instrument);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..09a9871 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1738,15 +1738,7 @@ static void
fireBSTriggers(ModifyTableState *node)
{
ModifyTable *plan = (ModifyTable *) node->ps.plan;
- ResultRelInfo *resultRelInfo = node->resultRelInfo;
-
- /*
- * If the node modifies a partitioned table, we must fire its triggers.
- * Note that in that case, node->resultRelInfo points to the first leaf
- * partition, not the root table.
- */
- if (node->rootResultRelInfo != NULL)
- resultRelInfo = node->rootResultRelInfo;
+ ResultRelInfo *resultRelInfo = getTargetResultRelInfo(node);
switch (node->operation)
{
@@ -1772,17 +1764,18 @@ fireBSTriggers(ModifyTableState *node)
* Return the target rel ResultRelInfo.
*
* This relation is the same as :
- * - the relation for which we will fire AFTER STATEMENT triggers.
+ * - the relation for which we will fire BEFIRE/AFTER STATEMENT triggers.
* - the relation into whose tuple format all captured transition tuples must
* be converted.
- * - the root partitioned table.
+ * - the root partitioned table mentioned in an UPDATE or DELETE query.
*/
static ResultRelInfo *
getTargetResultRelInfo(ModifyTableState *node)
{
/*
- * Note that if the node modifies a partitioned table, node->resultRelInfo
- * points to the first leaf partition, not the root table.
+ * Note that if the node performs an UPDATE or DELETE on a partitioned
+ * table, node->resultRelInfo points to the first leaf partition, not the
+ * root table.
*/
if (node->rootResultRelInfo != NULL)
return node->rootResultRelInfo;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b40a112..9486873 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2329,22 +2329,12 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
*/
if (parse->commandType != CMD_SELECT && !inheritance_update)
{
- Index rootRelation;
+ Index rootRelation = 0;
List *withCheckOptionLists;
List *returningLists;
List *rowMarks;
/*
- * If target is a partition root table, we need to mark the
- * ModifyTable node appropriately for that.
- */
- if (rt_fetch(parse->resultRelation, parse->rtable)->relkind ==
- RELKIND_PARTITIONED_TABLE)
- rootRelation = parse->resultRelation;
- else
- rootRelation = 0;
-
- /*
* Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
* needed.
*/
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index f3d1a12..05a0882 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -930,12 +930,17 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->resultRelations);
/*
- * If the main target relation is a partitioned table, also
- * add the partition root's RT index to rootResultRelations,
- * and remember its index in that list in rootResultRelIndex.
+ * If the main target relation of an inherited UPDATE/DELETE
+ * operation is a partitioned table, also add the partition
+ * root's RT index to rootResultRelations, and remember its
+ * index in that list in rootResultRelIndex. We don't need
+ * this for INSERT though as there are no other result
+ * relations present in query beside the partition root whose
+ * index is given by resultRelIndex.
*/
if (splan->rootRelation)
{
+ Assert(splan->operation != CMD_INSERT);
splan->rootResultRelIndex =
list_length(root->glob->rootResultRelations);
root->glob->rootResultRelations =
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 485d1b0..2a2db9c 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1813,7 +1813,9 @@ typedef struct ModifyTablePath
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
- Index rootRelation; /* Root RT index, if target is partitioned */
+ Index rootRelation; /* RT index of root partitioned target
+ * relation; valid only for UPDATE or DELETE,
+ * 0 for INSERT */
bool partColsUpdated; /* some part key in hierarchy updated */
List *resultRelations; /* integer list of RT indexes */
List *subpaths; /* Path(s) producing source data */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7314d2f..13043d1 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -221,11 +221,15 @@ typedef struct ModifyTable
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
- Index rootRelation; /* Root RT index, if target is partitioned */
+ Index rootRelation; /* RT index of root partitioned target
+ * relation; valid only for UPDATE or DELETE,
+ * 0 for INSERT */
bool partColsUpdated; /* some part key in hierarchy updated */
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in plan's list */
- int rootResultRelIndex; /* index of the partitioned table root */
+ int rootResultRelIndex; /* index of root partitioned target
+ * relation in plan's list; valid only for
+ * UPDATE or DELETE, -1 for INSERT */
List *plans; /* plan(s) producing source data */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
--
1.8.3.1
v4-0004-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v4-0004-Initialize-result-relation-information-lazily.patchDownload
From dd41fd415ce2de6db2694d90aae08d614b0e14b4 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v4 4/4] Initialize result relation information lazily
Currently, ResultRelInfo of all result relation appearing in
PlannedStmt.resultRelations, PlannedStmt.rootResultRelations are
initialized before execution begins. That can be wasteful as only
one or a handful of potentially many result relations appearing in
those lists (especially resultRelations) may actually have any rows
to update or delete.
This refactors ModifyTable code so as to delay creating ResultRelInfo
of a given result relation to when its subplan produces a tuple for
the first time, which in turn means that we don't make one for result
relations for which no tuples are found to be processed. This can
save some amount of work in the cases with many unpruned partitions
in the plan of which only one or handful need to be processed, such
as with generic plans. (Now, without support for runtime pruning of
UPDATE and DELETE subplans, initializing ResultRelInfos may not be
too much work when compared to initializing the subplans themselves,
but we may have runtime pruning in the future when we will not have
to worry about the overhead of initializing result rels.)
As part of this, any place that assumes that a given result relation
can be accessed by its index in ModifyTableState.resultRelInfo is
updated to instead get it using the new function ExecGetResultRelInfo
which checks if one exists and create one at the appropriate index if
not.
ModifyTableState gets a new field mt_done_rels that is a bitmapset of
RT indexes of result relations that have been fully processed due to
their subplans having exhausted tuples to process.
---
contrib/postgres_fdw/postgres_fdw.c | 6 +-
src/backend/commands/copy.c | 4 +-
src/backend/commands/explain.c | 40 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/executor/execMain.c | 123 ++---
src/backend/executor/execPartition.c | 111 ++--
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 849 ++++++++++++++++++-------------
src/backend/replication/logical/worker.c | 2 +-
src/include/executor/executor.h | 4 +
src/include/nodes/execnodes.h | 7 +-
11 files changed, 649 insertions(+), 503 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index fc53c23..abe55b7 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1943,7 +1943,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
if (plan && plan->operation == CMD_UPDATE &&
(resultRelInfo->ri_usesFdwDirectModify ||
resultRelInfo->ri_FdwState) &&
- resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan)
+ !list_member_int(mtstate->mt_done_rels, resultRelation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route tuples into foreign table to be updated \"%s\"",
@@ -1997,7 +1997,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
*/
if (plan && plan->operation == CMD_UPDATE &&
resultRelation == plan->rootRelation)
- resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+ resultRelation = linitial_int(plan->resultRelations);
}
/* Construct the SQL command string. */
@@ -2458,7 +2458,7 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = &estate->es_result_relations[dmstate->resultRelIndex];
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[dmstate->resultRelIndex];
/* The executor must have initialized the ResultRelInfo for us. */
Assert(resultRelInfo != NULL);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 155ac5b..739e526 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2838,7 +2838,7 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
@@ -2852,7 +2852,7 @@ CopyFrom(CopyState cstate)
mtstate->ps.plan = NULL;
mtstate->ps.state = estate;
mtstate->operation = CMD_INSERT;
- mtstate->resultRelInfo = estate->es_result_relations;
+ mtstate->resultRelInfo = resultRelInfo;
if (resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 30e0a7e..84ce07e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -795,13 +796,21 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
show_relname = (numrels > 1 || numrootrels > 0 ||
routerels != NIL || targrels != NIL);
- rInfo = queryDesc->estate->es_result_relations;
- for (nr = 0; nr < numrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ for (nr = 0; nr < numrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_result_relations[nr];
- rInfo = queryDesc->estate->es_root_result_relations;
- for (nr = 0; nr < numrootrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
+
+ for (nr = 0; nr < numrootrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_root_result_relations[nr];
+
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
foreach(l, routerels)
{
@@ -3681,15 +3690,28 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ linitial_int(node->resultRelations) != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
- FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
+ /*
+ * Fetch ResultRelInfo to show target relation information. Some or
+ * all ResultRelInfos may not have been built either because
+ * ModifyTable is not executed at all (with ANALYZE off), or some were
+ * not processed during execution (with ANALYZE on). We ask to create
+ * any missing ResultRelInfos by passing true for 'create_it'.
+ */
+ ResultRelInfo *resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex + j, true);
+ FdwRoutine *fdwroutine;
+
+ if (resultRelInfo == NULL)
+ continue;
+
+ fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ac53f79..b5bbf3a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1789,7 +1789,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
0);
resultRelInfo++;
}
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations = &resultRelInfos;
estate->es_num_result_relations = list_length(rels);
/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..6d560c3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,35 +828,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_plannedstmt = plannedstmt;
/*
- * Initialize ResultRelInfo data structures, and open the result rels.
+ * Allocate space for ResultRelInfo pointers that will be filled later.
+ * See ExecGetResultRelInfo() and ExecGetRootResultRelInfo().
*/
if (plannedstmt->resultRelations)
{
List *resultRelations = plannedstmt->resultRelations;
int numResultRelations = list_length(resultRelations);
- ResultRelInfo *resultRelInfos;
- ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, resultRelations)
- {
- Index resultRelationIndex = lfirst_int(l);
- Relation resultRelation;
-
- resultRelation = ExecGetRangeTableRelation(estate,
- resultRelationIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelation,
- resultRelationIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
estate->es_num_result_relations = numResultRelations;
-
/* es_result_relation_info is NULL except when within ModifyTable */
estate->es_result_relation_info = NULL;
@@ -869,25 +851,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
{
int num_roots = list_length(plannedstmt->rootResultRelations);
- resultRelInfos = (ResultRelInfo *)
- palloc(num_roots * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, plannedstmt->rootResultRelations)
- {
- Index resultRelIndex = lfirst_int(l);
- Relation resultRelDesc;
-
- resultRelDesc = ExecGetRangeTableRelation(estate,
- resultRelIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelDesc,
- resultRelIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
-
- estate->es_root_result_relations = resultRelInfos;
+ estate->es_root_result_relations =
+ palloc0(num_roots * sizeof(ResultRelInfo *));
estate->es_num_root_result_relations = num_roots;
}
else
@@ -1377,24 +1342,18 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
MemoryContext oldcontext;
/* First, search through the query result relations */
- rInfo = estate->es_result_relations;
- nr = estate->es_num_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/* Second, search through the root result relations, if any */
- rInfo = estate->es_root_result_relations;
- nr = estate->es_num_root_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_root_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_root_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/*
@@ -1561,13 +1520,20 @@ ExecEndPlan(PlanState *planstate, EState *estate)
/*
* close indexes of result relation(s) if any. (Rels themselves get
- * closed next.)
+ * closed next.) Also, allow the FDWs to shut down.
*/
- resultRelInfo = estate->es_result_relations;
- for (i = estate->es_num_result_relations; i > 0; i--)
+ for (i = 0; i < estate->es_num_result_relations; i++)
{
- ExecCloseIndices(resultRelInfo);
- resultRelInfo++;
+ resultRelInfo = estate->es_result_relations[i];
+ if (resultRelInfo)
+ {
+ ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
+ }
}
/*
@@ -2796,23 +2762,42 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree)
{
int numResultRelations = parentestate->es_num_result_relations;
int numRootResultRels = parentestate->es_num_root_result_relations;
- ResultRelInfo *resultRelInfos;
+ int i;
+ ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_result_relations,
- numResultRelations * sizeof(ResultRelInfo));
- rcestate->es_result_relations = resultRelInfos;
+ rcestate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
+ for (i = 0; i < numResultRelations; i++)
+ {
+ if (parentestate->es_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_result_relations = numResultRelations;
/* Also transfer partitioned root result relations. */
if (numRootResultRels > 0)
{
- resultRelInfos = (ResultRelInfo *)
- palloc(numRootResultRels * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_root_result_relations,
- numRootResultRels * sizeof(ResultRelInfo));
- rcestate->es_root_result_relations = resultRelInfos;
+ rcestate->es_root_result_relations =
+ palloc0(numRootResultRels * sizeof(ResultRelInfo *));
+ for (i = 0; i < numRootResultRels; i++)
+ {
+ if (parentestate->es_root_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_root_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_root_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_root_result_relations = numRootResultRels;
}
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 2ab41f1..4be5a25 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -151,7 +151,7 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
@@ -212,7 +212,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -234,18 +233,38 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
+ return proute;
+}
+
+/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
ExecHashSubPlanResultRelsByOid(mtstate, proute);
- return proute;
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ /*
+ * The UPDATE result relation may not have been processed and hence its
+ * ResultRelInfo not created yet, so pass true for 'create_it'.
+ */
+ if (elem)
+ return ExecGetResultRelInfo(mtstate,
+ node->resultRelIndex + elem->index,
+ true);
+ return NULL;
}
/*
@@ -352,7 +371,7 @@ ExecFindPartition(ModifyTableState *mtstate,
if (partdesc->is_leaf[partidx])
{
- ResultRelInfo *rri;
+ ResultRelInfo *rri = NULL;
/*
* Look to see if we've already got a ResultRelInfo for this
@@ -366,36 +385,31 @@ ExecFindPartition(ModifyTableState *mtstate,
}
else
{
- bool found = false;
-
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if it's also an UPDATE result relation, we might as
+ * well use the one UPDATE might use.
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
- {
- found = true;
- rri = elem->rri;
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
- /* Verify this ResultRelInfo allows INSERTs */
+ /* Verify this ResultRelInfo allows INSERTs */
+ if (rri)
+ {
CheckValidResultRel(rri, CMD_INSERT);
/* Set up the PartitionRoutingInfo for it */
+ rri->ri_PartitionRoot = proute->partition_root;
ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
rri, partidx);
}
}
- /* We need to create a new one. */
- if (!found)
+ /* Nope, We need to create a new one. */
+ if (rri == NULL)
rri = ExecInitPartitionInfo(mtstate, estate, proute,
dispatch,
rootResultRelInfo, partidx);
@@ -456,9 +470,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -469,26 +487,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -509,7 +527,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Index firstVarno = node ? linitial_int(node->resultRelations) : 0;
+ Relation firstResultRel = firstVarno > 0 ?
+ ExecGetRangeTableRelation(estate, firstVarno) : NULL;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -550,14 +570,13 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -621,7 +640,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -680,7 +698,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -931,8 +948,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
/*
* If the partition appears to be a reused UPDATE result relation, the
* necessary map would already have been set in ri_ChildToRootMap by
- * ExecInitModifyTable(), so use that one instead of building one from
- * scratch. One can tell if it's actually a reused UPDATE result
+ * ExecBuildResultRelInfo(), so use that one instead of building one
+ * from scratch. One can tell if it's actually a reused UPDATE result
* relation by looking at its ri_RangeTableIndex which must be
* different from the root RT index.
*/
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d0e65b8..a67c023 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -711,13 +711,13 @@ ExecCreateScanSlotFromOuterPlan(EState *estate,
bool
ExecRelationIsTargetRelation(EState *estate, Index scanrelid)
{
- ResultRelInfo *resultRelInfos;
+ ResultRelInfo **resultRelInfos;
int i;
resultRelInfos = estate->es_result_relations;
for (i = 0; i < estate->es_num_result_relations; i++)
{
- if (resultRelInfos[i].ri_RangeTableIndex == scanrelid)
+ if (resultRelInfos[i]->ri_RangeTableIndex == scanrelid)
return true;
}
return false;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f8f4254..43681d5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,6 +72,9 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
+static ResultRelInfo *ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex);
+static ResultRelInfo *ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int globalRelIndex);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -359,6 +362,405 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
MemoryContextSwitchTo(oldContext);
}
+/*
+ * ExecGetResultRelInfo
+ * Returns the result relation at a given offset in es_result_relations
+ *
+ * If not present and 'create_it' is true, it is created and put at the given
+ * offset for subsequent calls to find.
+ *
+ * This allows lazy initialization of ResultRelInfos. That can be helpful in
+ * the case where there are multiple result relations due to inheritance but
+ * only one or few actually end up actually having any tuples to process.
+ *
+ * Note: only call from the executor proper or anything that possesses a valid
+ * execution context, that is an EState with a PlannedStmt, because this
+ * depends on finding a valid PlannedStmt to get result relation RT indexes
+ * from.
+ */
+ResultRelInfo *
+ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[resultRelIndex];
+
+ if (resultRelInfo == NULL && create_it)
+ {
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
+ Index rti = list_nth_int(resultRelations, resultRelIndex);
+
+ Assert(mtstate != NULL && mtstate->ps.plan != NULL);
+ resultRelInfo = ExecBuildResultRelInfo(mtstate, rti, resultRelIndex);
+ estate->es_result_relations[resultRelIndex] = resultRelInfo;
+ }
+
+ return resultRelInfo;
+}
+
+/*
+ * ExecGetRootResultRelInfo
+ * Like ExecGetResultRelInfo, but for "root" result relations
+ * corresponding to partitioned tables, which are managed separately from
+ * leaf result relations
+ *
+ * Root ResultRelInfos are never created lazily, although it seems better to
+ * have the same interface to avoid exposing ExecBuildResultRelInfo().
+ */
+static ResultRelInfo *
+ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *rootRelInfo = estate->es_root_result_relations[rootRelIndex];
+
+ if (rootRelInfo == NULL)
+ {
+ List *rootRelations = estate->es_plannedstmt->rootResultRelations;
+ Index rti = list_nth_int(rootRelations, rootRelIndex);
+
+ Assert(mtstate != NULL && mtstate->ps.plan != NULL);
+ rootRelInfo = ExecBuildResultRelInfo(mtstate, rti, rootRelIndex);
+ estate->es_root_result_relations[rootRelIndex] = rootRelInfo;
+ }
+
+ return rootRelInfo;
+}
+
+/*
+ * ExecBuildResultRelInfo
+ * Builds a ResultRelInfo for a result relation with given RT index
+ *
+ * Beside creating the ResultRelInfo and setting its various fields based on
+ * the provided ModifyTable plan, this may also set some fields in the
+ * ModifyTableState.
+ */
+static ResultRelInfo *
+ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int globalRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ CmdType operation = node->operation;
+ int firstRelIndex = node->resultRelIndex;
+ int thisRelIndex = globalRelIndex - firstRelIndex;
+ Plan *subplan = mtstate->mt_plans[thisRelIndex]->plan;
+ Relation relation = ExecGetRangeTableRelation(estate, rti);
+ ResultRelInfo *resultRelInfo;
+ bool update_tuple_routing_needed = false;
+ ListCell *l;
+ int eflags = estate->es_top_eflags;
+ bool junk_filter_needed = false;
+ MemoryContext oldcxt;
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, relation, rti, NULL,
+ estate->es_instrument);
+
+ /*
+ * Verify result relation is a valid target for the current operation
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /*
+ * If this is the root result relation of an UPDATE/DELETE, it only needs
+ * to look minimally valid.
+ */
+ if (rti == node->rootRelation)
+ {
+ MemoryContextSwitchTo(oldcxt);
+ return resultRelInfo;
+ }
+
+ /*
+ * 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);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(thisRelIndex,
+ node->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists,
+ thisRelIndex);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ thisRelIndex,
+ eflags);
+ }
+
+ /*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ if (node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists,
+ thisRelIndex);
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* RETURNING list */
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists,
+ thisRelIndex);
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (node->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (node->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a slot
+ * of the table's type here, because the slot will be used to insert
+ * into the table, and for RETURNING processing - which may access
+ * system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(node->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (node->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) node->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize the junk filter(s) if needed. INSERT queries need a filter
+ * if there are any junk attrs in the tlist. UPDATE and DELETE always
+ * need a filter, since there's always at least one junk attribute present
+ * --- no need to look first. Typically, this will be a 'ctid' or
+ * 'wholerow' attribute, but in the case of a foreign data wrapper it
+ * might be a set of junk attributes sufficient to identify the remote
+ * row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
+ * can't be fooled by some needing a filter and some not.
+ *
+ * This section of code is also a convenient place to verify that the
+ * output of an INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ j = ExecInitJunkFilter(subplan->targetlist, junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+ else if (operation == CMD_INSERT)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * For UPDATE on a partitioned tables, tuple routing might be needed if
+ * if the plan says so or a BEFORE UPDATE trigger is present on the
+ * partition which might modify the partition-key values.
+ */
+ if (mtstate->rootResultRelInfo && operation == CMD_UPDATE &&
+ (node->partColsUpdated ||
+ (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_update_before_row)))
+ update_tuple_routing_needed = true;
+
+ /*
+ * 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. During INSERT, partition tuples to
+ * store into the transition tuple store are converted using
+ * PartitionToRoot map in the partition's PartitionRoutingInfo.
+ */
+ if (update_tuple_routing_needed ||
+ (mtstate->mt_transition_capture && operation != CMD_INSERT))
+ {
+ Relation targetRel = getTargetResultRelInfo(mtstate)->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ }
+
+ /* ModifyTableState changes follow.*/
+
+ /* Result relation specific slot to store the plan's output tuple. */
+ mtstate->mt_scans[thisRelIndex] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(mtstate->mt_plans[thisRelIndex]),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* Tuple routing state may already have been initialized. */
+ if (update_tuple_routing_needed &&
+ mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rootRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return resultRelInfo;
+}
+
/* ----------------------------------------------------------------
* ExecInsert
*
@@ -1896,13 +2298,14 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
+ int firstRelIndex = plan->resultRelIndex;
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
EState *estate = node->ps.state;
CmdType operation = node->operation;
ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -1943,9 +2346,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* es_result_relation_info must point to the currently active result
@@ -1956,8 +2357,6 @@ ExecModifyTable(PlanState *pstate)
*/
saved_resultRelInfo = estate->es_result_relation_info;
- estate->es_result_relation_info = resultRelInfo;
-
/*
* Fetch rows from subplan(s), and execute the required table modification
* for each row.
@@ -1980,18 +2379,41 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially creating it for the first time.
+ */
+ if (bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans) &&
+ resultRelInfo == NULL)
+ resultRelInfo = ExecGetResultRelInfo(node,
+ firstRelIndex + node->mt_whichplan,
+ true);
+
planSlot = ExecProcNode(subplanstate);
- if (TupIsNull(planSlot))
+ /*
+ * If we got a tuple to process and haven't initialized the
+ * ResultRelInfo to use for the subplan's target relation, fetch one
+ * if not not already done, potentially creating it for the first
+ * time.
+ */
+ if (!TupIsNull(planSlot) && resultRelInfo == NULL)
+ resultRelInfo = ExecGetResultRelInfo(node,
+ firstRelIndex + node->mt_whichplan,
+ true);
+ else if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Mark the current result rel as having been fully processed. */
+ node->mt_done_rels = lappend_int(node->mt_done_rels,
+ list_nth_int(plan->resultRelations,
+ node->mt_whichplan));
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
- estate->es_result_relation_info = resultRelInfo;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2000,6 +2422,9 @@ ExecModifyTable(PlanState *pstate)
break;
}
+ Assert(resultRelInfo != NULL);
+ estate->es_result_relation_info = resultRelInfo;
+
/*
* Ensure input tuple is the right format for the target relation.
*/
@@ -2034,8 +2459,10 @@ ExecModifyTable(PlanState *pstate)
tupleid = NULL;
oldtuple = NULL;
- if (junkfilter != NULL)
+ if (resultRelInfo->ri_junkFilter != NULL)
{
+ JunkFilter *junkfilter = resultRelInfo->ri_junkFilter;
+
/*
* extract the 'ctid' or 'wholerow' junk attribute.
*/
@@ -2167,14 +2594,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
Plan *subplan;
ListCell *l;
int i;
Relation rel;
- bool update_tuple_routing_needed = node->partColsUpdated;
- ResultRelInfo *rootResultRel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -2192,19 +2615,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_done = false;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
- mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
- /* If modifying a partitioned table, initialize the root table info */
- if (node->rootResultRelIndex >= 0)
- {
- mtstate->rootResultRelInfo = estate->es_root_result_relations +
- node->rootResultRelIndex;
- rootResultRel = mtstate->rootResultRelInfo;
- }
- else
- rootResultRel = mtstate->resultRelInfo;
-
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2213,197 +2625,33 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
/*
- * Build state for collecting transition tuples. This requires having a
- * valid trigger query context, so skip it in explain-only mode.
- */
- if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
- ExecSetupTransitionCaptureState(mtstate, estate);
-
- /*
* call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries. Note we *must* set
- * estate->es_result_relation_info correctly while we initialize each
- * sub-plan; external modules such as FDWs may depend on that (see
- * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one
- * example).
+ * results into the array "mt_plans".
*/
- saved_resultRelInfo = estate->es_result_relation_info;
-
- resultRelInfo = mtstate->resultRelInfo;
i = 0;
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
/* Now init the plan for this result rel */
- estate->es_result_relation_info = resultRelInfo;
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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. During INSERT, partition tuples to
- * store into the transition tuple store are converted using
- * PartitionToRoot map in the partition's PartitionRoutingInfo.
- */
- 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(rootResultRel->ri_RelationDesc));
- resultRelInfo++;
- i++;
- }
-
- estate->es_result_relation_info = saved_resultRelInfo;
-
- /* Get the target relation */
- rel = (getTargetResultRelInfo(mtstate))->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.
- */
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- (operation == CMD_INSERT || update_tuple_routing_needed))
- 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
}
- /*
- * Initialize RETURNING projections if needed.
- */
+ /* Initialize some global state for RETURNING projections. */
if (node->returningLists)
{
- TupleTableSlot *slot;
- ExprContext *econtext;
-
/*
* Initialize result tuple slot and assign its rowtype using the first
* RETURNING list. We assume the rest will look the same.
*/
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+ mtstate->ps.plan->targetlist = linitial(node->returningLists);
/* Set up a slot for the output of the RETURNING projection(s) */
ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
/* Need an econtext too */
if (mtstate->ps.ps_ExprContext == NULL)
ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
}
else
{
@@ -2417,67 +2665,51 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->ps.ps_ExprContext = NULL;
}
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
/*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
+ * Initialize the target relation for getTargetResultRelInfo() to return
+ * for this ModifyTableState. Other result relations, especially those
+ * for UPDATE and DELETE that have an RT index present in
+ * node->resultRelations, are initialized on as-needed basis during
+ * execution. INSERT or UPDATE tuple routing target partitions, which are
+ * not present in node->resultRelations, are also initialized on as-needed
+ * basis but managed by execPartition.c, not by nodeModifyTable.c.
+ *
+ * For UPDATE, DELETE on a partitioned table, the target relation is the
+ * "root" table given by node->rootResultRelIndex and
+ * node->resultRelations contains only leaf partitions. In all other cases,
+ * we initialize the first relation appearing in node->resultRelations as
+ * the target relation. For INSERTs, there's only one relation in
+ * node->resultRelations.
*/
- if (node->onConflictAction == ONCONFLICT_UPDATE)
+ if (node->rootResultRelIndex >= 0)
{
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ Assert(operation != CMD_INSERT);
+ mtstate->rootResultRelInfo =
+ ExecGetRootResultRelInfo(mtstate, node->rootResultRelIndex);
+ }
+ else
+ mtstate->resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex, true);
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
+ /* Get the target relation */
+ rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc;
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
+ /*
+ * Build state for tuple routing if it's an INSERT. If an UPDATE might
+ * need it, ExecBuildResultRelInfo will build it when initializing
+ * a partition's ResultRelInfo.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ operation == CMD_INSERT)
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rel);
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
+ /*
+ * Build state for collecting transition tuples. This requires having a
+ * valid trigger query context, so skip it in explain-only mode.
+ */
+ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+ ExecSetupTransitionCaptureState(mtstate, estate);
/*
* If we have any secondary relations in an UPDATE or DELETE, they need to
@@ -2515,109 +2747,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2647,20 +2776,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 2fcf2e6..689030b 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -213,7 +213,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
resultRelInfo = makeNode(ResultRelInfo);
InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 415e117..c65f0a8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -603,4 +603,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 647bb79..393aa49 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -530,7 +530,7 @@ typedef struct EState
CommandId es_output_cid;
/* Info about target table(s) for insert/update/delete queries: */
- ResultRelInfo *es_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_result_relations; /* array of ResultRelInfo pointers */
int es_num_result_relations; /* length of array */
ResultRelInfo *es_result_relation_info; /* currently active array elt */
@@ -540,7 +540,8 @@ typedef struct EState
* es_result_relations, but we need access to the roots for firing
* triggers and for runtime tuple routing.
*/
- ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_root_result_relations; /* array of ResultRelInfo
+ * pointers */
int es_num_root_result_relations; /* length of the array */
PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */
@@ -1177,6 +1178,8 @@ typedef struct ModifyTableState
PlanState **mt_plans; /* subplans (one per target rel) */
int mt_nplans; /* number of plans in the array */
int mt_whichplan; /* which one is being executed (0..n-1) */
+ List *mt_done_rels; /* RT indexes of result relations that have
+ * been fully processed. */
TupleTableSlot **mt_scans; /* input tuple corresponding to underlying
* plans */
ResultRelInfo *resultRelInfo; /* per-subplan target relations */
--
1.8.3.1
v4-0001-Revise-how-FDWs-obtain-result-relation-informatio.patchapplication/octet-stream; name=v4-0001-Revise-how-FDWs-obtain-result-relation-informatio.patchDownload
From 587c36be9c01e317148109bb2777a0b6d4884359 Mon Sep 17 00:00:00 2001
From: Etsuro Fujita <efujita@postgresql.org>
Date: Thu, 8 Aug 2019 21:41:12 +0900
Subject: [PATCH v4 1/4] Revise how FDWs obtain result relation information
For BeginDirectModify, which is called when ExecInitModifyTable
initializes source plans, the only way currently to access information
about the foreign table being modified is through ResultRelInfo for
that table passed by setting EState.es_result_relation_info.
This commit installs a new field in ForeignScan node to pass
this information directly instead of through ResultRelInfo.
This provides two benefits:
1. Reduce the reliance on es_result_relation_info (a query-global
variable) being set correctly by the calling code, which can be
bug-prone especially in the case of an inherited update.
2. Allows ResultRelInfos to be created at a later stage of ModifyTable
execution instead of during ExecInitModifyTable.
Amit Langote, Etsuro Fujita
---
contrib/postgres_fdw/postgres_fdw.c | 26 ++++++++++++++++++--------
doc/src/sgml/fdwhandler.sgml | 10 ++++++----
src/backend/executor/nodeForeignscan.c | 5 ++++-
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 8 ++++++++
src/backend/optimizer/plan/setrefs.c | 15 +++++++++++++++
src/include/nodes/plannodes.h | 3 +++
9 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9fc53ca..fc53c23 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -200,6 +200,9 @@ typedef struct PgFdwDirectModifyState
Relation rel; /* relcache entry for the foreign table */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
+ int resultRelIndex; /* index of ResultRelInfo for the foreign table
+ * in EState.es_result_relations */
+
/* extracted fdw_private data */
char *query; /* text of UPDATE/DELETE command */
bool has_returning; /* is there a RETURNING clause? */
@@ -446,11 +449,12 @@ static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
static void execute_dml_stmt(ForeignScanState *node);
-static TupleTableSlot *get_returning_data(ForeignScanState *node);
+static TupleTableSlot *get_returning_data(ForeignScanState *node, ResultRelInfo *resultRelInfo);
static void init_returning_filter(PgFdwDirectModifyState *dmstate,
List *fdw_scan_tlist,
Index rtindex);
static TupleTableSlot *apply_returning_filter(PgFdwDirectModifyState *dmstate,
+ ResultRelInfo *relInfo,
TupleTableSlot *slot,
EState *estate);
static void prepare_query_params(PlanState *node,
@@ -2332,6 +2336,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
{
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
PgFdwDirectModifyState *dmstate;
Index rtindex;
RangeTblEntry *rte;
@@ -2356,7 +2361,9 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does.
*/
- rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
+ Assert(fsplan->resultRelIndex >= 0);
+ dmstate->resultRelIndex = fsplan->resultRelIndex;
+ rtindex = list_nth_int(resultRelations, fsplan->resultRelIndex);
rte = exec_rt_fetch(rtindex, estate);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
@@ -2451,7 +2458,10 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = &estate->es_result_relations[dmstate->resultRelIndex];
+
+ /* The executor must have initialized the ResultRelInfo for us. */
+ Assert(resultRelInfo != NULL);
/*
* If this is the first call after Begin, execute the statement.
@@ -2483,7 +2493,7 @@ postgresIterateDirectModify(ForeignScanState *node)
/*
* Get the next RETURNING tuple.
*/
- return get_returning_data(node);
+ return get_returning_data(node, resultRelInfo);
}
/*
@@ -4083,11 +4093,10 @@ execute_dml_stmt(ForeignScanState *node)
* Get the result of a RETURNING clause.
*/
static TupleTableSlot *
-get_returning_data(ForeignScanState *node)
+get_returning_data(ForeignScanState *node, ResultRelInfo *resultRelInfo)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
TupleTableSlot *resultSlot;
@@ -4142,7 +4151,8 @@ get_returning_data(ForeignScanState *node)
if (dmstate->rel)
resultSlot = slot;
else
- resultSlot = apply_returning_filter(dmstate, slot, estate);
+ resultSlot = apply_returning_filter(dmstate, resultRelInfo, slot,
+ estate);
}
dmstate->next_tuple++;
@@ -4231,10 +4241,10 @@ init_returning_filter(PgFdwDirectModifyState *dmstate,
*/
static TupleTableSlot *
apply_returning_filter(PgFdwDirectModifyState *dmstate,
+ ResultRelInfo *relInfo,
TupleTableSlot *slot,
EState *estate)
{
- ResultRelInfo *relInfo = estate->es_result_relation_info;
TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
TupleTableSlot *resultSlot;
Datum *values;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 7479303..54154ee 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -892,8 +892,9 @@ BeginDirectModify(ForeignScanState *node,
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
<structname>ForeignScanState</structname> node (in particular, from the underlying
- <structname>ForeignScan</structname> plan node, which contains any FDW-private
- information provided by <function>PlanDirectModify</function>).
+ <structname>ForeignScan</structname> plan node, which contains an integer field
+ giving the table's index in the query's list of result relations along with any
+ FDW-private information provided by <function>PlanDirectModify</function>.
<literal>eflags</literal> contains flag bits describing the executor's
operating mode for this plan node.
</para>
@@ -925,8 +926,9 @@ IterateDirectModify(ForeignScanState *node);
tuple table slot (the node's <structfield>ScanTupleSlot</structfield> should be
used for this purpose). The data that was actually inserted, updated
or deleted must be stored in the
- <literal>es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
- of the node's <structname>EState</structname>.
+ <literal>ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
+ of the target foreign table's <structname>ResultRelInfo</structname>
+ obtained using the information passed to <function>BeginDirectModify</function>.
Return NULL if no more rows are available.
Note that this is called in a short-lived memory context that will be
reset between invocations. Create a memory context in
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 513471a..19433b3 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -221,10 +221,13 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan or the direct modification.
*/
if (node->operation != CMD_SELECT)
+ {
+ Assert(node->resultRelIndex >= 0);
fdwroutine->BeginDirectModify(scanstate, eflags);
+ }
else
fdwroutine->BeginForeignScan(scanstate, eflags);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409d..2afb195 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -761,6 +761,7 @@ _copyForeignScan(const ForeignScan *from)
COPY_NODE_FIELD(fdw_recheck_quals);
COPY_BITMAPSET_FIELD(fs_relids);
COPY_SCALAR_FIELD(fsSystemCol);
+ COPY_SCALAR_FIELD(resultRelIndex);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f1775..15fd85a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -698,6 +698,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
WRITE_NODE_FIELD(fdw_recheck_quals);
WRITE_BITMAPSET_FIELD(fs_relids);
WRITE_BOOL_FIELD(fsSystemCol);
+ WRITE_INT_FIELD(resultRelIndex);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab..4024a80 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2017,6 +2017,7 @@ _readForeignScan(void)
READ_NODE_FIELD(fdw_recheck_quals);
READ_BITMAPSET_FIELD(fs_relids);
READ_BOOL_FIELD(fsSystemCol);
+ READ_INT_FIELD(resultRelIndex);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 99278ee..4e86249 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -5541,6 +5541,8 @@ make_foreignscan(List *qptlist,
node->fs_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;
+ /* resultRelIndex will be set by make_modifytable(), if needed */
+ node->resultRelIndex = -1;
return node;
}
@@ -6900,7 +6902,13 @@ make_modifytable(PlannerInfo *root,
!has_stored_generated_columns(subroot, rti))
direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
if (direct_modify)
+ {
+ ForeignScan *fscan = (ForeignScan *) list_nth(subplans, i);
+
+ Assert(IsA(fscan, ForeignScan));
+ fscan->resultRelIndex = i;
direct_modify_plans = bms_add_member(direct_modify_plans, i);
+ }
if (!direct_modify &&
fdwroutine != NULL &&
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index baefe0e..f3d1a12 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -904,6 +904,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
rc->rti += rtoffset;
rc->prti += rtoffset;
}
+ /*
+ * Caution: Do not change the relative ordering of this loop
+ * and the statement below that adds the result relations to
+ * root->glob->resultRelations, because we need to use the
+ * current value of list_length(root->glob->resultRelations)
+ * in some plans.
+ */
foreach(l, splan->plans)
{
lfirst(l) = set_plan_refs(root,
@@ -1243,6 +1250,14 @@ set_foreignscan_references(PlannerInfo *root,
}
fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
+
+ /*
+ * Adjust resultRelIndex if it's valid (note that we are called before
+ * adding the RT indexes of ModifyTable result relations to the global
+ * list)
+ */
+ if (fscan->resultRelIndex >= 0)
+ fscan->resultRelIndex += list_length(root->glob->resultRelations);
}
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 83e0107..7314d2f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -620,6 +620,9 @@ typedef struct ForeignScan
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
Bitmapset *fs_relids; /* RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
+ int resultRelIndex; /* index of foreign table in the list of query
+ * result relations for INSERT/UPDATE/DELETE;
+ * -1 for SELECT */
} ForeignScan;
/* ----------------
--
1.8.3.1
Hello,
On Fri, Aug 7, 2020 at 9:26 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Tue, Aug 4, 2020 at 3:15 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Sat, Aug 1, 2020 at 4:46 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jun 26, 2020 at 8:36 AM Amit Langote <amitlangote09@gmail.com> wrote:
0001 and 0002 are preparatory patches.
I read through these patches a bit but it's really unclear what the
point of them is. I think they need better commit messages, or better
comments, or both.Thanks for taking a look. Sorry about the lack of good commentary,
which I have tried to address in the attached updated version. I
extracted one more part as preparatory from the earlier 0003 patch, so
there are 4 patches now.Also as discussed with Daniel, I have changed the patches so that they
can be applied on plain HEAD instead of having to first apply the
patches at [1]. Without runtime pruning for UPDATE/DELETE proposed in
[1], optimizing ResultRelInfo creation by itself does not improve the
performance/scalability by that much, but the benefit of lazily
creating ResultRelInfos seems clear so I think maybe it's okay to
pursue this independently.Per cfbot's automatic patch tester, there were some issues in the 0004 patch:
nodeModifyTable.c: In function ‘ExecModifyTable’:
1529nodeModifyTable.c:2484:24: error: ‘junkfilter’ may be used
uninitialized in this function [-Werror=maybe-uninitialized]
1530 junkfilter->jf_junkAttNo,
1531 ^
1532nodeModifyTable.c:2309:14: note: ‘junkfilter’ was declared here
1533 JunkFilter *junkfilter;
1534 ^
1535cc1: all warnings being treated as errors
1536<builtin>: recipe for target 'nodeModifyTable.o' failed
1537make[3]: *** [nodeModifyTable.o] Error 1Fixed in the attached updated version
Needed a rebase due to f481d28232. Attached updated patches.
--
Amit Langote
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v5-0004-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v5-0004-Initialize-result-relation-information-lazily.patchDownload
From 0e96dd251079ed034c129619f3b2893b4d45c2a7 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v5 4/4] Initialize result relation information lazily
Currently, ResultRelInfo of all result relation appearing in
PlannedStmt.resultRelations, PlannedStmt.rootResultRelations are
initialized before execution begins. That can be wasteful as only
one or a handful of potentially many result relations appearing in
those lists (especially resultRelations) may actually have any rows
to update or delete.
This refactors ModifyTable code so as to delay creating ResultRelInfo
of a given result relation to when its subplan produces a tuple for
the first time, which in turn means that we don't make one for result
relations for which no tuples are found to be processed. This can
save some amount of work in the cases with many unpruned partitions
in the plan of which only one or handful need to be processed, such
as with generic plans. (Now, without support for runtime pruning of
UPDATE and DELETE subplans, initializing ResultRelInfos may not be
too much work when compared to initializing the subplans themselves,
but we may have runtime pruning in the future when we will not have
to worry about the overhead of initializing result rels.)
As part of this, any place that assumes that a given result relation
can be accessed by its index in ModifyTableState.resultRelInfo is
updated to instead get it using the new function ExecGetResultRelInfo
which checks if one exists and create one at the appropriate index if
not.
ModifyTableState gets a new field mt_done_rels that is a bitmapset of
RT indexes of result relations that have been fully processed due to
their subplans having exhausted tuples to process.
---
contrib/postgres_fdw/postgres_fdw.c | 6 +-
src/backend/commands/copy.c | 4 +-
src/backend/commands/explain.c | 40 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/executor/execMain.c | 123 ++---
src/backend/executor/execPartition.c | 111 ++--
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 849 ++++++++++++++++++-------------
src/backend/replication/logical/worker.c | 2 +-
src/include/executor/executor.h | 4 +
src/include/nodes/execnodes.h | 7 +-
11 files changed, 650 insertions(+), 502 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 13e256f..f6a36c3 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1942,7 +1942,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
if (plan && plan->operation == CMD_UPDATE &&
(resultRelInfo->ri_usesFdwDirectModify ||
resultRelInfo->ri_FdwState) &&
- resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan)
+ !list_member_int(mtstate->mt_done_rels, resultRelation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route tuples into foreign table to be updated \"%s\"",
@@ -1996,7 +1996,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
*/
if (plan && plan->operation == CMD_UPDATE &&
resultRelation == plan->rootRelation)
- resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+ resultRelation = linitial_int(plan->resultRelations);
}
/* Construct the SQL command string. */
@@ -2457,7 +2457,7 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = &estate->es_result_relations[dmstate->resultRelIndex];
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[dmstate->resultRelIndex];
/* The executor must have initialized the ResultRelInfo for us. */
Assert(resultRelInfo != NULL);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 155ac5b..739e526 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2838,7 +2838,7 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
@@ -2852,7 +2852,7 @@ CopyFrom(CopyState cstate)
mtstate->ps.plan = NULL;
mtstate->ps.state = estate;
mtstate->operation = CMD_INSERT;
- mtstate->resultRelInfo = estate->es_result_relations;
+ mtstate->resultRelInfo = resultRelInfo;
if (resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c98c9b5..139181d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -783,13 +784,21 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
show_relname = (numrels > 1 || numrootrels > 0 ||
routerels != NIL || targrels != NIL);
- rInfo = queryDesc->estate->es_result_relations;
- for (nr = 0; nr < numrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ for (nr = 0; nr < numrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_result_relations[nr];
- rInfo = queryDesc->estate->es_root_result_relations;
- for (nr = 0; nr < numrootrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es);
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
+
+ for (nr = 0; nr < numrootrels; nr++)
+ {
+ rInfo = queryDesc->estate->es_root_result_relations[nr];
+
+ if (rInfo)
+ report_triggers(rInfo, show_relname, es);
+ }
foreach(l, routerels)
{
@@ -3681,15 +3690,28 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ linitial_int(node->resultRelations) != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
- FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
+ /*
+ * Fetch ResultRelInfo to show target relation information. Some or
+ * all ResultRelInfos may not have been built either because
+ * ModifyTable is not executed at all (with ANALYZE off), or some were
+ * not processed during execution (with ANALYZE on). We ask to create
+ * any missing ResultRelInfos by passing true for 'create_it'.
+ */
+ ResultRelInfo *resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex + j, true);
+ FdwRoutine *fdwroutine;
+
+ if (resultRelInfo == NULL)
+ continue;
+
+ fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e57c7f..1f01785 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1802,7 +1802,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
0);
resultRelInfo++;
}
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations = &resultRelInfos;
estate->es_num_result_relations = list_length(rels);
/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..6d560c3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,35 +828,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_plannedstmt = plannedstmt;
/*
- * Initialize ResultRelInfo data structures, and open the result rels.
+ * Allocate space for ResultRelInfo pointers that will be filled later.
+ * See ExecGetResultRelInfo() and ExecGetRootResultRelInfo().
*/
if (plannedstmt->resultRelations)
{
List *resultRelations = plannedstmt->resultRelations;
int numResultRelations = list_length(resultRelations);
- ResultRelInfo *resultRelInfos;
- ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, resultRelations)
- {
- Index resultRelationIndex = lfirst_int(l);
- Relation resultRelation;
-
- resultRelation = ExecGetRangeTableRelation(estate,
- resultRelationIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelation,
- resultRelationIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
- estate->es_result_relations = resultRelInfos;
+ estate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
estate->es_num_result_relations = numResultRelations;
-
/* es_result_relation_info is NULL except when within ModifyTable */
estate->es_result_relation_info = NULL;
@@ -869,25 +851,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
{
int num_roots = list_length(plannedstmt->rootResultRelations);
- resultRelInfos = (ResultRelInfo *)
- palloc(num_roots * sizeof(ResultRelInfo));
- resultRelInfo = resultRelInfos;
- foreach(l, plannedstmt->rootResultRelations)
- {
- Index resultRelIndex = lfirst_int(l);
- Relation resultRelDesc;
-
- resultRelDesc = ExecGetRangeTableRelation(estate,
- resultRelIndex);
- InitResultRelInfo(resultRelInfo,
- resultRelDesc,
- resultRelIndex,
- NULL,
- estate->es_instrument);
- resultRelInfo++;
- }
-
- estate->es_root_result_relations = resultRelInfos;
+ estate->es_root_result_relations =
+ palloc0(num_roots * sizeof(ResultRelInfo *));
estate->es_num_root_result_relations = num_roots;
}
else
@@ -1377,24 +1342,18 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
MemoryContext oldcontext;
/* First, search through the query result relations */
- rInfo = estate->es_result_relations;
- nr = estate->es_num_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/* Second, search through the root result relations, if any */
- rInfo = estate->es_root_result_relations;
- nr = estate->es_num_root_result_relations;
- while (nr > 0)
+ for (nr = 0; nr < estate->es_num_root_result_relations; nr++)
{
- if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ rInfo = estate->es_root_result_relations[nr];
+ if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid)
return rInfo;
- rInfo++;
- nr--;
}
/*
@@ -1561,13 +1520,20 @@ ExecEndPlan(PlanState *planstate, EState *estate)
/*
* close indexes of result relation(s) if any. (Rels themselves get
- * closed next.)
+ * closed next.) Also, allow the FDWs to shut down.
*/
- resultRelInfo = estate->es_result_relations;
- for (i = estate->es_num_result_relations; i > 0; i--)
+ for (i = 0; i < estate->es_num_result_relations; i++)
{
- ExecCloseIndices(resultRelInfo);
- resultRelInfo++;
+ resultRelInfo = estate->es_result_relations[i];
+ if (resultRelInfo)
+ {
+ ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
+ }
}
/*
@@ -2796,23 +2762,42 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree)
{
int numResultRelations = parentestate->es_num_result_relations;
int numRootResultRels = parentestate->es_num_root_result_relations;
- ResultRelInfo *resultRelInfos;
+ int i;
+ ResultRelInfo *resultRelInfo;
- resultRelInfos = (ResultRelInfo *)
- palloc(numResultRelations * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_result_relations,
- numResultRelations * sizeof(ResultRelInfo));
- rcestate->es_result_relations = resultRelInfos;
+ rcestate->es_result_relations =
+ palloc0(numResultRelations * sizeof(ResultRelInfo *));
+ for (i = 0; i < numResultRelations; i++)
+ {
+ if (parentestate->es_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_result_relations = numResultRelations;
/* Also transfer partitioned root result relations. */
if (numRootResultRels > 0)
{
- resultRelInfos = (ResultRelInfo *)
- palloc(numRootResultRels * sizeof(ResultRelInfo));
- memcpy(resultRelInfos, parentestate->es_root_result_relations,
- numRootResultRels * sizeof(ResultRelInfo));
- rcestate->es_root_result_relations = resultRelInfos;
+ rcestate->es_root_result_relations =
+ palloc0(numRootResultRels * sizeof(ResultRelInfo *));
+ for (i = 0; i < numRootResultRels; i++)
+ {
+ if (parentestate->es_root_result_relations[i])
+ {
+ resultRelInfo = makeNode(ResultRelInfo);
+ memcpy(resultRelInfo, parentestate->es_root_result_relations[i],
+ sizeof(ResultRelInfo));
+ }
+ else
+ resultRelInfo = NULL;
+ rcestate->es_root_result_relations[i] = resultRelInfo;
+ }
rcestate->es_num_root_result_relations = numRootResultRels;
}
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 852cf33..caee2f1 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -157,7 +157,7 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
@@ -218,7 +218,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,18 +239,38 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
+ return proute;
+}
+
+/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
ExecHashSubPlanResultRelsByOid(mtstate, proute);
- return proute;
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ /*
+ * The UPDATE result relation may not have been processed and hence its
+ * ResultRelInfo not created yet, so pass true for 'create_it'.
+ */
+ if (elem)
+ return ExecGetResultRelInfo(mtstate,
+ node->resultRelIndex + elem->index,
+ true);
+ return NULL;
}
/*
@@ -348,6 +367,8 @@ ExecFindPartition(ModifyTableState *mtstate,
if (partdesc->is_leaf[partidx])
{
+ rri = NULL;
+
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -360,36 +381,31 @@ ExecFindPartition(ModifyTableState *mtstate,
}
else
{
- bool found = false;
-
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if it's also an UPDATE result relation, we might as
+ * well use the one UPDATE might use.
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
- {
- found = true;
- rri = elem->rri;
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
- /* Verify this ResultRelInfo allows INSERTs */
+ /* Verify this ResultRelInfo allows INSERTs */
+ if (rri)
+ {
CheckValidResultRel(rri, CMD_INSERT);
/* Set up the PartitionRoutingInfo for it */
+ rri->ri_PartitionRoot = proute->partition_root;
ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
rri, partidx);
}
}
- /* We need to create a new one. */
- if (!found)
+ /* Nope, We need to create a new one. */
+ if (rri == NULL)
rri = ExecInitPartitionInfo(mtstate, estate, proute,
dispatch,
rootResultRelInfo, partidx);
@@ -513,9 +529,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -526,26 +546,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -566,7 +586,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Index firstVarno = node ? linitial_int(node->resultRelations) : 0;
+ Relation firstResultRel = firstVarno > 0 ?
+ ExecGetRangeTableRelation(estate, firstVarno) : NULL;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -607,14 +629,13 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -678,7 +699,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -737,7 +757,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -988,8 +1007,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
/*
* If the partition appears to be a reused UPDATE result relation, the
* necessary map would already have been set in ri_ChildToRootMap by
- * ExecInitModifyTable(), so use that one instead of building one from
- * scratch. One can tell if it's actually a reused UPDATE result
+ * ExecBuildResultRelInfo(), so use that one instead of building one
+ * from scratch. One can tell if it's actually a reused UPDATE result
* relation by looking at its ri_RangeTableIndex which must be
* different from the root RT index.
*/
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d0e65b8..a67c023 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -711,13 +711,13 @@ ExecCreateScanSlotFromOuterPlan(EState *estate,
bool
ExecRelationIsTargetRelation(EState *estate, Index scanrelid)
{
- ResultRelInfo *resultRelInfos;
+ ResultRelInfo **resultRelInfos;
int i;
resultRelInfos = estate->es_result_relations;
for (i = 0; i < estate->es_num_result_relations; i++)
{
- if (resultRelInfos[i].ri_RangeTableIndex == scanrelid)
+ if (resultRelInfos[i]->ri_RangeTableIndex == scanrelid)
return true;
}
return false;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f8f4254..43681d5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,6 +72,9 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
+static ResultRelInfo *ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex);
+static ResultRelInfo *ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int globalRelIndex);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -359,6 +362,405 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
MemoryContextSwitchTo(oldContext);
}
+/*
+ * ExecGetResultRelInfo
+ * Returns the result relation at a given offset in es_result_relations
+ *
+ * If not present and 'create_it' is true, it is created and put at the given
+ * offset for subsequent calls to find.
+ *
+ * This allows lazy initialization of ResultRelInfos. That can be helpful in
+ * the case where there are multiple result relations due to inheritance but
+ * only one or few actually end up actually having any tuples to process.
+ *
+ * Note: only call from the executor proper or anything that possesses a valid
+ * execution context, that is an EState with a PlannedStmt, because this
+ * depends on finding a valid PlannedStmt to get result relation RT indexes
+ * from.
+ */
+ResultRelInfo *
+ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *resultRelInfo = estate->es_result_relations[resultRelIndex];
+
+ if (resultRelInfo == NULL && create_it)
+ {
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
+ Index rti = list_nth_int(resultRelations, resultRelIndex);
+
+ Assert(mtstate != NULL && mtstate->ps.plan != NULL);
+ resultRelInfo = ExecBuildResultRelInfo(mtstate, rti, resultRelIndex);
+ estate->es_result_relations[resultRelIndex] = resultRelInfo;
+ }
+
+ return resultRelInfo;
+}
+
+/*
+ * ExecGetRootResultRelInfo
+ * Like ExecGetResultRelInfo, but for "root" result relations
+ * corresponding to partitioned tables, which are managed separately from
+ * leaf result relations
+ *
+ * Root ResultRelInfos are never created lazily, although it seems better to
+ * have the same interface to avoid exposing ExecBuildResultRelInfo().
+ */
+static ResultRelInfo *
+ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ResultRelInfo *rootRelInfo = estate->es_root_result_relations[rootRelIndex];
+
+ if (rootRelInfo == NULL)
+ {
+ List *rootRelations = estate->es_plannedstmt->rootResultRelations;
+ Index rti = list_nth_int(rootRelations, rootRelIndex);
+
+ Assert(mtstate != NULL && mtstate->ps.plan != NULL);
+ rootRelInfo = ExecBuildResultRelInfo(mtstate, rti, rootRelIndex);
+ estate->es_root_result_relations[rootRelIndex] = rootRelInfo;
+ }
+
+ return rootRelInfo;
+}
+
+/*
+ * ExecBuildResultRelInfo
+ * Builds a ResultRelInfo for a result relation with given RT index
+ *
+ * Beside creating the ResultRelInfo and setting its various fields based on
+ * the provided ModifyTable plan, this may also set some fields in the
+ * ModifyTableState.
+ */
+static ResultRelInfo *
+ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti,
+ int globalRelIndex)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ CmdType operation = node->operation;
+ int firstRelIndex = node->resultRelIndex;
+ int thisRelIndex = globalRelIndex - firstRelIndex;
+ Plan *subplan = mtstate->mt_plans[thisRelIndex]->plan;
+ Relation relation = ExecGetRangeTableRelation(estate, rti);
+ ResultRelInfo *resultRelInfo;
+ bool update_tuple_routing_needed = false;
+ ListCell *l;
+ int eflags = estate->es_top_eflags;
+ bool junk_filter_needed = false;
+ MemoryContext oldcxt;
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, relation, rti, NULL,
+ estate->es_instrument);
+
+ /*
+ * Verify result relation is a valid target for the current operation
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /*
+ * If this is the root result relation of an UPDATE/DELETE, it only needs
+ * to look minimally valid.
+ */
+ if (rti == node->rootRelation)
+ {
+ MemoryContextSwitchTo(oldcxt);
+ return resultRelInfo;
+ }
+
+ /*
+ * 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);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(thisRelIndex,
+ node->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists,
+ thisRelIndex);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ thisRelIndex,
+ eflags);
+ }
+
+ /*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ if (node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists,
+ thisRelIndex);
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* RETURNING list */
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists,
+ thisRelIndex);
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (node->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (node->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a slot
+ * of the table's type here, because the slot will be used to insert
+ * into the table, and for RETURNING processing - which may access
+ * system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(node->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (node->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) node->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize the junk filter(s) if needed. INSERT queries need a filter
+ * if there are any junk attrs in the tlist. UPDATE and DELETE always
+ * need a filter, since there's always at least one junk attribute present
+ * --- no need to look first. Typically, this will be a 'ctid' or
+ * 'wholerow' attribute, but in the case of a foreign data wrapper it
+ * might be a set of junk attributes sufficient to identify the remote
+ * row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
+ * can't be fooled by some needing a filter and some not.
+ *
+ * This section of code is also a convenient place to verify that the
+ * output of an INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ j = ExecInitJunkFilter(subplan->targetlist, junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+ else if (operation == CMD_INSERT)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * For UPDATE on a partitioned tables, tuple routing might be needed if
+ * if the plan says so or a BEFORE UPDATE trigger is present on the
+ * partition which might modify the partition-key values.
+ */
+ if (mtstate->rootResultRelInfo && operation == CMD_UPDATE &&
+ (node->partColsUpdated ||
+ (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_update_before_row)))
+ update_tuple_routing_needed = true;
+
+ /*
+ * 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. During INSERT, partition tuples to
+ * store into the transition tuple store are converted using
+ * PartitionToRoot map in the partition's PartitionRoutingInfo.
+ */
+ if (update_tuple_routing_needed ||
+ (mtstate->mt_transition_capture && operation != CMD_INSERT))
+ {
+ Relation targetRel = getTargetResultRelInfo(mtstate)->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ }
+
+ /* ModifyTableState changes follow.*/
+
+ /* Result relation specific slot to store the plan's output tuple. */
+ mtstate->mt_scans[thisRelIndex] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(mtstate->mt_plans[thisRelIndex]),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* Tuple routing state may already have been initialized. */
+ if (update_tuple_routing_needed &&
+ mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rootRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return resultRelInfo;
+}
+
/* ----------------------------------------------------------------
* ExecInsert
*
@@ -1896,13 +2298,14 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
+ int firstRelIndex = plan->resultRelIndex;
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
EState *estate = node->ps.state;
CmdType operation = node->operation;
ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -1943,9 +2346,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* es_result_relation_info must point to the currently active result
@@ -1956,8 +2357,6 @@ ExecModifyTable(PlanState *pstate)
*/
saved_resultRelInfo = estate->es_result_relation_info;
- estate->es_result_relation_info = resultRelInfo;
-
/*
* Fetch rows from subplan(s), and execute the required table modification
* for each row.
@@ -1980,18 +2379,41 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially creating it for the first time.
+ */
+ if (bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans) &&
+ resultRelInfo == NULL)
+ resultRelInfo = ExecGetResultRelInfo(node,
+ firstRelIndex + node->mt_whichplan,
+ true);
+
planSlot = ExecProcNode(subplanstate);
- if (TupIsNull(planSlot))
+ /*
+ * If we got a tuple to process and haven't initialized the
+ * ResultRelInfo to use for the subplan's target relation, fetch one
+ * if not not already done, potentially creating it for the first
+ * time.
+ */
+ if (!TupIsNull(planSlot) && resultRelInfo == NULL)
+ resultRelInfo = ExecGetResultRelInfo(node,
+ firstRelIndex + node->mt_whichplan,
+ true);
+ else if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Mark the current result rel as having been fully processed. */
+ node->mt_done_rels = lappend_int(node->mt_done_rels,
+ list_nth_int(plan->resultRelations,
+ node->mt_whichplan));
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
- estate->es_result_relation_info = resultRelInfo;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2000,6 +2422,9 @@ ExecModifyTable(PlanState *pstate)
break;
}
+ Assert(resultRelInfo != NULL);
+ estate->es_result_relation_info = resultRelInfo;
+
/*
* Ensure input tuple is the right format for the target relation.
*/
@@ -2034,8 +2459,10 @@ ExecModifyTable(PlanState *pstate)
tupleid = NULL;
oldtuple = NULL;
- if (junkfilter != NULL)
+ if (resultRelInfo->ri_junkFilter != NULL)
{
+ JunkFilter *junkfilter = resultRelInfo->ri_junkFilter;
+
/*
* extract the 'ctid' or 'wholerow' junk attribute.
*/
@@ -2167,14 +2594,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo;
Plan *subplan;
ListCell *l;
int i;
Relation rel;
- bool update_tuple_routing_needed = node->partColsUpdated;
- ResultRelInfo *rootResultRel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -2192,19 +2615,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_done = false;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
- mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
- /* If modifying a partitioned table, initialize the root table info */
- if (node->rootResultRelIndex >= 0)
- {
- mtstate->rootResultRelInfo = estate->es_root_result_relations +
- node->rootResultRelIndex;
- rootResultRel = mtstate->rootResultRelInfo;
- }
- else
- rootResultRel = mtstate->resultRelInfo;
-
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2213,197 +2625,33 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
/*
- * Build state for collecting transition tuples. This requires having a
- * valid trigger query context, so skip it in explain-only mode.
- */
- if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
- ExecSetupTransitionCaptureState(mtstate, estate);
-
- /*
* call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries. Note we *must* set
- * estate->es_result_relation_info correctly while we initialize each
- * sub-plan; external modules such as FDWs may depend on that (see
- * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one
- * example).
+ * results into the array "mt_plans".
*/
- saved_resultRelInfo = estate->es_result_relation_info;
-
- resultRelInfo = mtstate->resultRelInfo;
i = 0;
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
/* Now init the plan for this result rel */
- estate->es_result_relation_info = resultRelInfo;
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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. During INSERT, partition tuples to
- * store into the transition tuple store are converted using
- * PartitionToRoot map in the partition's PartitionRoutingInfo.
- */
- 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(rootResultRel->ri_RelationDesc));
- resultRelInfo++;
- i++;
- }
-
- estate->es_result_relation_info = saved_resultRelInfo;
-
- /* Get the target relation */
- rel = (getTargetResultRelInfo(mtstate))->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.
- */
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- (operation == CMD_INSERT || update_tuple_routing_needed))
- 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
}
- /*
- * Initialize RETURNING projections if needed.
- */
+ /* Initialize some global state for RETURNING projections. */
if (node->returningLists)
{
- TupleTableSlot *slot;
- ExprContext *econtext;
-
/*
* Initialize result tuple slot and assign its rowtype using the first
* RETURNING list. We assume the rest will look the same.
*/
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+ mtstate->ps.plan->targetlist = linitial(node->returningLists);
/* Set up a slot for the output of the RETURNING projection(s) */
ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
/* Need an econtext too */
if (mtstate->ps.ps_ExprContext == NULL)
ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
}
else
{
@@ -2417,67 +2665,51 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->ps.ps_ExprContext = NULL;
}
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
/*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
+ * Initialize the target relation for getTargetResultRelInfo() to return
+ * for this ModifyTableState. Other result relations, especially those
+ * for UPDATE and DELETE that have an RT index present in
+ * node->resultRelations, are initialized on as-needed basis during
+ * execution. INSERT or UPDATE tuple routing target partitions, which are
+ * not present in node->resultRelations, are also initialized on as-needed
+ * basis but managed by execPartition.c, not by nodeModifyTable.c.
+ *
+ * For UPDATE, DELETE on a partitioned table, the target relation is the
+ * "root" table given by node->rootResultRelIndex and
+ * node->resultRelations contains only leaf partitions. In all other cases,
+ * we initialize the first relation appearing in node->resultRelations as
+ * the target relation. For INSERTs, there's only one relation in
+ * node->resultRelations.
*/
- if (node->onConflictAction == ONCONFLICT_UPDATE)
+ if (node->rootResultRelIndex >= 0)
{
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ Assert(operation != CMD_INSERT);
+ mtstate->rootResultRelInfo =
+ ExecGetRootResultRelInfo(mtstate, node->rootResultRelIndex);
+ }
+ else
+ mtstate->resultRelInfo =
+ ExecGetResultRelInfo(mtstate, node->resultRelIndex, true);
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
+ /* Get the target relation */
+ rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc;
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
+ /*
+ * Build state for tuple routing if it's an INSERT. If an UPDATE might
+ * need it, ExecBuildResultRelInfo will build it when initializing
+ * a partition's ResultRelInfo.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ operation == CMD_INSERT)
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rel);
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
+ /*
+ * Build state for collecting transition tuples. This requires having a
+ * valid trigger query context, so skip it in explain-only mode.
+ */
+ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+ ExecSetupTransitionCaptureState(mtstate, estate);
/*
* If we have any secondary relations in an UPDATE or DELETE, they need to
@@ -2515,109 +2747,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2647,20 +2776,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index c37aafe..b5737db 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -359,7 +359,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
resultRelInfo = makeNode(ResultRelInfo);
InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
- estate->es_result_relations = resultRelInfo;
+ estate->es_result_relations = &resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 415e117..c65f0a8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -603,4 +603,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex,
+ bool create_it);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7351c57..b7cb488 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -530,7 +530,7 @@ typedef struct EState
CommandId es_output_cid;
/* Info about target table(s) for insert/update/delete queries: */
- ResultRelInfo *es_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_result_relations; /* array of ResultRelInfo pointers */
int es_num_result_relations; /* length of array */
ResultRelInfo *es_result_relation_info; /* currently active array elt */
@@ -540,7 +540,8 @@ typedef struct EState
* es_result_relations, but we need access to the roots for firing
* triggers and for runtime tuple routing.
*/
- ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */
+ ResultRelInfo **es_root_result_relations; /* array of ResultRelInfo
+ * pointers */
int es_num_root_result_relations; /* length of the array */
PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */
@@ -1179,6 +1180,8 @@ typedef struct ModifyTableState
PlanState **mt_plans; /* subplans (one per target rel) */
int mt_nplans; /* number of plans in the array */
int mt_whichplan; /* which one is being executed (0..n-1) */
+ List *mt_done_rels; /* RT indexes of result relations that have
+ * been fully processed. */
TupleTableSlot **mt_scans; /* input tuple corresponding to underlying
* plans */
ResultRelInfo *resultRelInfo; /* per-subplan target relations */
--
1.8.3.1
v5-0001-Revise-how-FDWs-obtain-result-relation-informatio.patchapplication/octet-stream; name=v5-0001-Revise-how-FDWs-obtain-result-relation-informatio.patchDownload
From 90ad2544925d5fba2fb4a6381c7154d0703b076d Mon Sep 17 00:00:00 2001
From: Etsuro Fujita <efujita@postgresql.org>
Date: Thu, 8 Aug 2019 21:41:12 +0900
Subject: [PATCH v5 1/4] Revise how FDWs obtain result relation information
For BeginDirectModify, which is called when ExecInitModifyTable
initializes source plans, the only way currently to access information
about the foreign table being modified is through ResultRelInfo for
that table passed by setting EState.es_result_relation_info.
This commit installs a new field in ForeignScan node to pass
this information directly instead of through ResultRelInfo.
This provides two benefits:
1. Reduce the reliance on es_result_relation_info (a query-global
variable) being set correctly by the calling code, which can be
bug-prone especially in the case of an inherited update.
2. Allows ResultRelInfos to be created at a later stage of ModifyTable
execution instead of during ExecInitModifyTable.
Amit Langote, Etsuro Fujita
---
contrib/postgres_fdw/postgres_fdw.c | 26 ++++++++++++++++++--------
doc/src/sgml/fdwhandler.sgml | 10 ++++++----
src/backend/executor/nodeForeignscan.c | 5 ++++-
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 8 ++++++++
src/backend/optimizer/plan/setrefs.c | 15 +++++++++++++++
src/include/nodes/plannodes.h | 3 +++
9 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index a31abce..13e256f 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -200,6 +200,9 @@ typedef struct PgFdwDirectModifyState
Relation rel; /* relcache entry for the foreign table */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
+ int resultRelIndex; /* index of ResultRelInfo for the foreign table
+ * in EState.es_result_relations */
+
/* extracted fdw_private data */
char *query; /* text of UPDATE/DELETE command */
bool has_returning; /* is there a RETURNING clause? */
@@ -446,11 +449,12 @@ static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
static void execute_dml_stmt(ForeignScanState *node);
-static TupleTableSlot *get_returning_data(ForeignScanState *node);
+static TupleTableSlot *get_returning_data(ForeignScanState *node, ResultRelInfo *resultRelInfo);
static void init_returning_filter(PgFdwDirectModifyState *dmstate,
List *fdw_scan_tlist,
Index rtindex);
static TupleTableSlot *apply_returning_filter(PgFdwDirectModifyState *dmstate,
+ ResultRelInfo *relInfo,
TupleTableSlot *slot,
EState *estate);
static void prepare_query_params(PlanState *node,
@@ -2331,6 +2335,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
{
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
+ List *resultRelations = estate->es_plannedstmt->resultRelations;
PgFdwDirectModifyState *dmstate;
Index rtindex;
RangeTblEntry *rte;
@@ -2355,7 +2360,9 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does.
*/
- rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
+ Assert(fsplan->resultRelIndex >= 0);
+ dmstate->resultRelIndex = fsplan->resultRelIndex;
+ rtindex = list_nth_int(resultRelations, fsplan->resultRelIndex);
rte = exec_rt_fetch(rtindex, estate);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
@@ -2450,7 +2457,10 @@ postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ ResultRelInfo *resultRelInfo = &estate->es_result_relations[dmstate->resultRelIndex];
+
+ /* The executor must have initialized the ResultRelInfo for us. */
+ Assert(resultRelInfo != NULL);
/*
* If this is the first call after Begin, execute the statement.
@@ -2482,7 +2492,7 @@ postgresIterateDirectModify(ForeignScanState *node)
/*
* Get the next RETURNING tuple.
*/
- return get_returning_data(node);
+ return get_returning_data(node, resultRelInfo);
}
/*
@@ -4082,11 +4092,10 @@ execute_dml_stmt(ForeignScanState *node)
* Get the result of a RETURNING clause.
*/
static TupleTableSlot *
-get_returning_data(ForeignScanState *node)
+get_returning_data(ForeignScanState *node, ResultRelInfo *resultRelInfo)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
TupleTableSlot *resultSlot;
@@ -4141,7 +4150,8 @@ get_returning_data(ForeignScanState *node)
if (dmstate->rel)
resultSlot = slot;
else
- resultSlot = apply_returning_filter(dmstate, slot, estate);
+ resultSlot = apply_returning_filter(dmstate, resultRelInfo, slot,
+ estate);
}
dmstate->next_tuple++;
@@ -4230,10 +4240,10 @@ init_returning_filter(PgFdwDirectModifyState *dmstate,
*/
static TupleTableSlot *
apply_returning_filter(PgFdwDirectModifyState *dmstate,
+ ResultRelInfo *relInfo,
TupleTableSlot *slot,
EState *estate)
{
- ResultRelInfo *relInfo = estate->es_result_relation_info;
TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
TupleTableSlot *resultSlot;
Datum *values;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 72fa127..1f6de9b 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -893,8 +893,9 @@ BeginDirectModify(ForeignScanState *node,
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
<structname>ForeignScanState</structname> node (in particular, from the underlying
- <structname>ForeignScan</structname> plan node, which contains any FDW-private
- information provided by <function>PlanDirectModify</function>).
+ <structname>ForeignScan</structname> plan node, which contains an integer field
+ giving the table's index in the query's list of result relations along with any
+ FDW-private information provided by <function>PlanDirectModify</function>.
<literal>eflags</literal> contains flag bits describing the executor's
operating mode for this plan node.
</para>
@@ -926,8 +927,9 @@ IterateDirectModify(ForeignScanState *node);
tuple table slot (the node's <structfield>ScanTupleSlot</structfield> should be
used for this purpose). The data that was actually inserted, updated
or deleted must be stored in the
- <literal>es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
- of the node's <structname>EState</structname>.
+ <literal>ri_projectReturning->pi_exprContext->ecxt_scantuple</literal>
+ of the target foreign table's <structname>ResultRelInfo</structname>
+ obtained using the information passed to <function>BeginDirectModify</function>.
Return NULL if no more rows are available.
Note that this is called in a short-lived memory context that will be
reset between invocations. Create a memory context in
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 513471a..19433b3 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -221,10 +221,13 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan or the direct modification.
*/
if (node->operation != CMD_SELECT)
+ {
+ Assert(node->resultRelIndex >= 0);
fdwroutine->BeginDirectModify(scanstate, eflags);
+ }
else
fdwroutine->BeginForeignScan(scanstate, eflags);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40..de57744 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -761,6 +761,7 @@ _copyForeignScan(const ForeignScan *from)
COPY_NODE_FIELD(fdw_recheck_quals);
COPY_BITMAPSET_FIELD(fs_relids);
COPY_SCALAR_FIELD(fsSystemCol);
+ COPY_SCALAR_FIELD(resultRelIndex);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f1775..15fd85a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -698,6 +698,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
WRITE_NODE_FIELD(fdw_recheck_quals);
WRITE_BITMAPSET_FIELD(fs_relids);
WRITE_BOOL_FIELD(fsSystemCol);
+ WRITE_INT_FIELD(resultRelIndex);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab..4024a80 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2017,6 +2017,7 @@ _readForeignScan(void)
READ_NODE_FIELD(fdw_recheck_quals);
READ_BITMAPSET_FIELD(fs_relids);
READ_BOOL_FIELD(fsSystemCol);
+ READ_INT_FIELD(resultRelIndex);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 99278ee..4e86249 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -5541,6 +5541,8 @@ make_foreignscan(List *qptlist,
node->fs_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;
+ /* resultRelIndex will be set by make_modifytable(), if needed */
+ node->resultRelIndex = -1;
return node;
}
@@ -6900,7 +6902,13 @@ make_modifytable(PlannerInfo *root,
!has_stored_generated_columns(subroot, rti))
direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
if (direct_modify)
+ {
+ ForeignScan *fscan = (ForeignScan *) list_nth(subplans, i);
+
+ Assert(IsA(fscan, ForeignScan));
+ fscan->resultRelIndex = i;
direct_modify_plans = bms_add_member(direct_modify_plans, i);
+ }
if (!direct_modify &&
fdwroutine != NULL &&
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index baefe0e..f3d1a12 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -904,6 +904,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
rc->rti += rtoffset;
rc->prti += rtoffset;
}
+ /*
+ * Caution: Do not change the relative ordering of this loop
+ * and the statement below that adds the result relations to
+ * root->glob->resultRelations, because we need to use the
+ * current value of list_length(root->glob->resultRelations)
+ * in some plans.
+ */
foreach(l, splan->plans)
{
lfirst(l) = set_plan_refs(root,
@@ -1243,6 +1250,14 @@ set_foreignscan_references(PlannerInfo *root,
}
fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
+
+ /*
+ * Adjust resultRelIndex if it's valid (note that we are called before
+ * adding the RT indexes of ModifyTable result relations to the global
+ * list)
+ */
+ if (fscan->resultRelIndex >= 0)
+ fscan->resultRelIndex += list_length(root->glob->resultRelations);
}
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 83e0107..7314d2f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -620,6 +620,9 @@ typedef struct ForeignScan
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
Bitmapset *fs_relids; /* RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
+ int resultRelIndex; /* index of foreign table in the list of query
+ * result relations for INSERT/UPDATE/DELETE;
+ * -1 for SELECT */
} ForeignScan;
/* ----------------
--
1.8.3.1
v5-0002-Don-t-make-root-ResultRelInfo-for-insert-queries.patchapplication/octet-stream; name=v5-0002-Don-t-make-root-ResultRelInfo-for-insert-queries.patchDownload
From 59c501c6010619e460782c1f40cf047d8dd29a75 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 18 Jun 2020 13:12:21 +0900
Subject: [PATCH v5 2/4] Don't make "root" ResultRelInfo for insert queries
For inserts on partitioned tables, we don't need a separate
ResultRelInfo for the root partitioned table, which being the query's
main target relation already has one, unlike UPDATE and DELETE where
only the leaf partitions are in the list of target relations.
We need a ResultRelInfo for the root partitioned table in the UPDATE
and DELETE cases so as to fire statement triggers on them. Also, in
the UPDATE's case it is used as the target result relation when
moving a row from one row to another.
---
src/backend/executor/execPartition.c | 2 +-
src/backend/executor/nodeModifyTable.c | 19 ++++++-------------
src/backend/optimizer/plan/planner.c | 12 +-----------
src/backend/optimizer/plan/setrefs.c | 11 ++++++++---
src/include/nodes/pathnodes.h | 4 +++-
src/include/nodes/plannodes.h | 8 ++++++--
6 files changed, 25 insertions(+), 31 deletions(-)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index bd2ea25..6e04fb3 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -579,7 +579,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
leaf_part_rri = makeNode(ResultRelInfo);
InitResultRelInfo(leaf_part_rri,
partrel,
- node ? node->rootRelation : 1,
+ rootResultRelInfo->ri_RangeTableIndex,
rootrel,
estate->es_instrument);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..09a9871 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1738,15 +1738,7 @@ static void
fireBSTriggers(ModifyTableState *node)
{
ModifyTable *plan = (ModifyTable *) node->ps.plan;
- ResultRelInfo *resultRelInfo = node->resultRelInfo;
-
- /*
- * If the node modifies a partitioned table, we must fire its triggers.
- * Note that in that case, node->resultRelInfo points to the first leaf
- * partition, not the root table.
- */
- if (node->rootResultRelInfo != NULL)
- resultRelInfo = node->rootResultRelInfo;
+ ResultRelInfo *resultRelInfo = getTargetResultRelInfo(node);
switch (node->operation)
{
@@ -1772,17 +1764,18 @@ fireBSTriggers(ModifyTableState *node)
* Return the target rel ResultRelInfo.
*
* This relation is the same as :
- * - the relation for which we will fire AFTER STATEMENT triggers.
+ * - the relation for which we will fire BEFIRE/AFTER STATEMENT triggers.
* - the relation into whose tuple format all captured transition tuples must
* be converted.
- * - the root partitioned table.
+ * - the root partitioned table mentioned in an UPDATE or DELETE query.
*/
static ResultRelInfo *
getTargetResultRelInfo(ModifyTableState *node)
{
/*
- * Note that if the node modifies a partitioned table, node->resultRelInfo
- * points to the first leaf partition, not the root table.
+ * Note that if the node performs an UPDATE or DELETE on a partitioned
+ * table, node->resultRelInfo points to the first leaf partition, not the
+ * root table.
*/
if (node->rootResultRelInfo != NULL)
return node->rootResultRelInfo;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 139c5e3..5e2e857 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2329,22 +2329,12 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
*/
if (parse->commandType != CMD_SELECT && !inheritance_update)
{
- Index rootRelation;
+ Index rootRelation = 0;
List *withCheckOptionLists;
List *returningLists;
List *rowMarks;
/*
- * If target is a partition root table, we need to mark the
- * ModifyTable node appropriately for that.
- */
- if (rt_fetch(parse->resultRelation, parse->rtable)->relkind ==
- RELKIND_PARTITIONED_TABLE)
- rootRelation = parse->resultRelation;
- else
- rootRelation = 0;
-
- /*
* Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
* needed.
*/
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index f3d1a12..05a0882 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -930,12 +930,17 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->resultRelations);
/*
- * If the main target relation is a partitioned table, also
- * add the partition root's RT index to rootResultRelations,
- * and remember its index in that list in rootResultRelIndex.
+ * If the main target relation of an inherited UPDATE/DELETE
+ * operation is a partitioned table, also add the partition
+ * root's RT index to rootResultRelations, and remember its
+ * index in that list in rootResultRelIndex. We don't need
+ * this for INSERT though as there are no other result
+ * relations present in query beside the partition root whose
+ * index is given by resultRelIndex.
*/
if (splan->rootRelation)
{
+ Assert(splan->operation != CMD_INSERT);
splan->rootResultRelIndex =
list_length(root->glob->rootResultRelations);
root->glob->rootResultRelations =
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 485d1b0..2a2db9c 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1813,7 +1813,9 @@ typedef struct ModifyTablePath
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
- Index rootRelation; /* Root RT index, if target is partitioned */
+ Index rootRelation; /* RT index of root partitioned target
+ * relation; valid only for UPDATE or DELETE,
+ * 0 for INSERT */
bool partColsUpdated; /* some part key in hierarchy updated */
List *resultRelations; /* integer list of RT indexes */
List *subpaths; /* Path(s) producing source data */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7314d2f..13043d1 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -221,11 +221,15 @@ typedef struct ModifyTable
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
- Index rootRelation; /* Root RT index, if target is partitioned */
+ Index rootRelation; /* RT index of root partitioned target
+ * relation; valid only for UPDATE or DELETE,
+ * 0 for INSERT */
bool partColsUpdated; /* some part key in hierarchy updated */
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in plan's list */
- int rootResultRelIndex; /* index of the partitioned table root */
+ int rootResultRelIndex; /* index of root partitioned target
+ * relation in plan's list; valid only for
+ * UPDATE or DELETE, -1 for INSERT */
List *plans; /* plan(s) producing source data */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
--
1.8.3.1
v5-0003-Revise-child-to-root-tuple-conversion-map-managem.patchapplication/octet-stream; name=v5-0003-Revise-child-to-root-tuple-conversion-map-managem.patchDownload
From 2ecdb6f2b6088cb11151590f5226e31f6bcffe5d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 30 Jul 2019 10:51:35 +0900
Subject: [PATCH v5 3/4] Revise child-to-root tuple conversion map management
Transition tuple capture requires to convert child tuples to the
inheritance root table format because that's the format the
transition tuplestore stores tuple in. For INSERTs into partitioned
tables, the conversion is handled by tuple routing code which
constructs the map for a given partition only if the partition is
targeted, but for UPDATE and DELETE, maps for all result relations
are made and stored in an array in ModifyTableState during
ExecInitModifyTable, which requires their ResultRelInfos to have been
already built. During execution, map for the currently active result
relation is set in TransitionCaptureState.tcs_map.
This commit removes TransitionCaptureMap.tcs_map in favor a new
map field in ResultRelInfo named ri_ChildToRootMap that is
initialized when the ResultRelInfo for a given result relation is.
This way is less confusing and less bug-prone than setting and
resetting tcs_map. Also, this will also allow us to delay creating
the map for a given result relation to when that relation is actually
processed during execution.
---
src/backend/commands/copy.c | 30 +----
src/backend/commands/trigger.c | 9 +-
src/backend/executor/execPartition.c | 20 +++-
src/backend/executor/nodeModifyTable.c | 203 ++++++++-------------------------
src/include/commands/trigger.h | 10 +-
src/include/nodes/execnodes.h | 11 +-
6 files changed, 85 insertions(+), 198 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db7d24a..155ac5b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3117,32 +3117,14 @@ CopyFrom(CopyState cstate)
estate->es_result_relation_info = resultRelInfo;
/*
- * If we're capturing transition tuples, we might need to convert
- * from the partition rowtype to root rowtype.
+ * If we're capturing transition tuples and there are no BEFORE
+ * triggers on the partition which may change the tuple, we can
+ * just remember the original unconverted tuple to avoid a
+ * needless round trip conversion.
*/
if (cstate->transition_capture != NULL)
- {
- if (has_before_insert_row_trig)
- {
- /*
- * If there are any BEFORE triggers on the partition,
- * we'll have to be ready to convert their result back to
- * tuplestore format.
- */
- cstate->transition_capture->tcs_original_insert_tuple = NULL;
- cstate->transition_capture->tcs_map =
- resultRelInfo->ri_PartitionInfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted
- * tuple, to avoid a needless round trip conversion.
- */
- cstate->transition_capture->tcs_original_insert_tuple = myslot;
- cstate->transition_capture->tcs_map = NULL;
- }
- }
+ cstate->transition_capture->tcs_original_insert_tuple =
+ !has_before_insert_row_trig ? myslot : NULL;
/*
* We might need to convert from the root rowtype to the partition
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..d1b5a03 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -35,6 +35,7 @@
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
+#include "executor/execPartition.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
@@ -4293,8 +4294,8 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
* tables, then return NULL.
*
* The resulting object can be passed to the ExecAR* functions. The caller
- * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing
- * with child tables.
+ * should set tcs_original_insert_tuple as appropriate when dealing with child
+ * tables
*
* Note that we copy the flags from a parent table into this struct (rather
* than subsequently using the relation's TriggerDesc directly) so that we can
@@ -5389,7 +5390,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = transition_capture->tcs_map;
+ PartitionRoutingInfo *pinfo = relinfo->ri_PartitionInfo;
+ TupleConversionMap *map = pinfo ? pinfo->pi_PartitionToRootMap :
+ relinfo->ri_ChildToRootMap;
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 6e04fb3..852cf33 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -983,9 +983,23 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
if (mtstate &&
(mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
{
- partrouteinfo->pi_PartitionToRootMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
- RelationGetDescr(partRelInfo->ri_PartitionRoot));
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+
+ /*
+ * If the partition appears to be a reused UPDATE result relation, the
+ * necessary map would already have been set in ri_ChildToRootMap by
+ * ExecInitModifyTable(), so use that one instead of building one from
+ * scratch. One can tell if it's actually a reused UPDATE result
+ * relation by looking at its ri_RangeTableIndex which must be
+ * different from the root RT index.
+ */
+ if (node && node->operation == CMD_UPDATE &&
+ node->rootRelation != partRelInfo->ri_RangeTableIndex)
+ partrouteinfo->pi_PartitionToRootMap = partRelInfo->ri_ChildToRootMap;
+ else
+ partrouteinfo->pi_PartitionToRootMap =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
+ RelationGetDescr(partRelInfo->ri_PartitionRoot));
}
else
partrouteinfo->pi_PartitionToRootMap = NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 09a9871..f8f4254 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -72,9 +72,6 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
-static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
-static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
- int whichplan);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1080,7 +1077,6 @@ ExecUpdate(ModifyTableState *mtstate,
TM_Result result;
TM_FailureData tmfd;
List *recheckIndexes = NIL;
- TupleConversionMap *saved_tcs_map = NULL;
/*
* abort the operation if not running transactions
@@ -1205,7 +1201,6 @@ lreplace:;
TupleTableSlot *ret_slot;
TupleTableSlot *epqslot = NULL;
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
- int map_index;
TupleConversionMap *tupconv_map;
/*
@@ -1275,26 +1270,11 @@ lreplace:;
}
/*
- * Updates set the transition capture map only when a new subplan
- * is chosen. But for inserts, it is set for each row. So after
- * INSERT, we need to revert back to the map created for UPDATE;
- * otherwise the next UPDATE will incorrectly use the one created
- * for INSERT. So first save the one created for UPDATE.
- */
- if (mtstate->mt_transition_capture)
- saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
-
- /*
* resultRelInfo is one of the per-subplan resultRelInfos. So we
* should convert the tuple into root's tuple descriptor, since
- * ExecInsert() starts the search from root. The tuple conversion
- * map list is in the order of mtstate->resultRelInfo[], so to
- * retrieve the one for this resultRel, we need to know the
- * position of the resultRel in mtstate->resultRelInfo[].
+ * ExecInsert() starts the search from root.
*/
- map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
- tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
+ tupconv_map = resultRelInfo->ri_ChildToRootMap;
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1313,11 +1293,13 @@ lreplace:;
/* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
+
+ /*
+ * Reset the transition state that may possibly have been written
+ * by INSERT.
+ */
if (mtstate->mt_transition_capture)
- {
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map = saved_tcs_map;
- }
return ret_slot;
}
@@ -1837,28 +1819,6 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
RelationGetRelid(targetRelInfo->ri_RelationDesc),
CMD_UPDATE);
-
- /*
- * If we found that we need to collect transition tuples then we may also
- * need tuple conversion maps for any children that have TupleDescs that
- * aren't compatible with the tuplestores. (We can share these maps
- * between the regular and ON CONFLICT cases.)
- */
- if (mtstate->mt_transition_capture != NULL ||
- mtstate->mt_oc_transition_capture != NULL)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
-
- /*
- * Install the conversion map for the first plan for UPDATE and DELETE
- * operations. It will be advanced each time we switch to the next
- * plan. (INSERT operations set it every time, so we need not update
- * mtstate->mt_oc_transition_capture here.)
- */
- if (mtstate->mt_transition_capture && mtstate->operation != CMD_INSERT)
- mtstate->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(mtstate, 0);
- }
}
/*
@@ -1882,6 +1842,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *partrel;
PartitionRoutingInfo *partrouteinfo;
TupleConversionMap *map;
+ bool has_before_insert_row_trig;
/*
* Lookup the target partition's ResultRelInfo. If ExecFindPartition does
@@ -1900,37 +1861,15 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
estate->es_result_relation_info = partrel;
/*
- * If we're capturing transition tuples, we might need to convert from the
- * partition rowtype to root partitioned table's rowtype.
+ * If we're capturing transition tuples and there are no BEFORE triggers
+ * on the partition which may change the tuple, we can just remember the
+ * original unconverted tuple to avoid a needless round trip conversion.
*/
+ has_before_insert_row_trig = (partrel->ri_TrigDesc &&
+ partrel->ri_TrigDesc->trig_insert_before_row);
if (mtstate->mt_transition_capture != NULL)
- {
- if (partrel->ri_TrigDesc &&
- partrel->ri_TrigDesc->trig_insert_before_row)
- {
- /*
- * If there are any BEFORE triggers on the partition, we'll have
- * to be ready to convert their result back to tuplestore format.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
- mtstate->mt_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted tuple, to
- * avoid a needless round trip conversion.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
- mtstate->mt_transition_capture->tcs_map = NULL;
- }
- }
- if (mtstate->mt_oc_transition_capture != NULL)
- {
- mtstate->mt_oc_transition_capture->tcs_map =
- partrouteinfo->pi_PartitionToRootMap;
- }
+ mtstate->mt_transition_capture->tcs_original_insert_tuple =
+ !has_before_insert_row_trig ? slot : NULL;
/*
* Convert the tuple, if necessary.
@@ -1946,58 +1885,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
return slot;
}
-/*
- * Initialize the child-to-root tuple conversion map array for UPDATE subplans.
- *
- * This map array is required to convert the tuple from the subplan result rel
- * to the target table descriptor. This requirement arises for two independent
- * scenarios:
- * 1. For update-tuple-routing.
- * 2. For capturing tuples in transition tables.
- */
-static void
-ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
-{
- ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
- ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
- TupleDesc outdesc;
- int numResultRelInfos = mtstate->mt_nplans;
- int i;
-
- /*
- * Build array of conversion maps from each child's TupleDesc to the one
- * used in the target relation. The map pointers may be NULL when no
- * conversion is necessary, which is hopefully a common case.
- */
-
- /* Get tuple descriptor of the target rel. */
- outdesc = RelationGetDescr(targetRelInfo->ri_RelationDesc);
-
- mtstate->mt_per_subplan_tupconv_maps = (TupleConversionMap **)
- palloc(sizeof(TupleConversionMap *) * numResultRelInfos);
-
- for (i = 0; i < numResultRelInfos; ++i)
- {
- mtstate->mt_per_subplan_tupconv_maps[i] =
- convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
- outdesc);
- }
-}
-
-/*
- * For a given subplan index, get the tuple conversion map.
- */
-static TupleConversionMap *
-tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
-{
- /* If nobody else set the per-subplan array of maps, do so ourselves. */
- if (mtstate->mt_per_subplan_tupconv_maps == NULL)
- ExecSetupChildParentMapForSubplan(mtstate);
-
- Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
- return mtstate->mt_per_subplan_tupconv_maps[whichplan];
-}
-
/* ----------------------------------------------------------------
* ExecModifyTable
*
@@ -2107,17 +1994,6 @@ ExecModifyTable(PlanState *pstate)
estate->es_result_relation_info = resultRelInfo;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
- /* Prepare to convert transition tuples from this child. */
- if (node->mt_transition_capture != NULL)
- {
- node->mt_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichplan);
- }
- if (node->mt_oc_transition_capture != NULL)
- {
- node->mt_oc_transition_capture->tcs_map =
- tupconv_map_for_subplan(node, node->mt_whichplan);
- }
continue;
}
else
@@ -2298,6 +2174,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
int i;
Relation rel;
bool update_tuple_routing_needed = node->partColsUpdated;
+ ResultRelInfo *rootResultRel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -2320,8 +2197,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
/* If modifying a partitioned table, initialize the root table info */
if (node->rootResultRelIndex >= 0)
+ {
mtstate->rootResultRelInfo = estate->es_root_result_relations +
node->rootResultRelIndex;
+ rootResultRel = mtstate->rootResultRelInfo;
+ }
+ else
+ rootResultRel = mtstate->resultRelInfo;
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
@@ -2331,6 +2213,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->fireBSTriggers = true;
/*
+ * Build state for collecting transition tuples. This requires having a
+ * valid trigger query context, so skip it in explain-only mode.
+ */
+ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+ ExecSetupTransitionCaptureState(mtstate, estate);
+
+ /*
* call ExecInitNode on each of the plans to be executed and save the
* results into the array "mt_plans". This is also a convenient place to
* verify that the proposed target relations are valid and open their
@@ -2403,6 +2292,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
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. During INSERT, partition tuples to
+ * store into the transition tuple store are converted using
+ * PartitionToRoot map in the partition's PartitionRoutingInfo.
+ */
+ 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(rootResultRel->ri_RelationDesc));
resultRelInfo++;
i++;
}
@@ -2429,26 +2334,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupPartitionTupleRouting(estate, mtstate, rel);
/*
- * Build state for collecting transition tuples. This requires having a
- * valid trigger query context, so skip it in explain-only mode.
- */
- if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
- ExecSetupTransitionCaptureState(mtstate, estate);
-
- /*
- * Construct mapping from each of the per-subplan partition attnos to the
- * root attno. This is required when during update row movement the tuple
- * descriptor of a source partition does not match the root partitioned
- * table descriptor. In such a case we need to convert tuples to the root
- * tuple descriptor, because the search for destination partition starts
- * from the root. We'll also need a slot to store these converted tuples.
- * We can skip this setup if it's not a partition key update.
+ * 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)
- {
- ExecSetupChildParentMapForSubplan(mtstate);
mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
- }
/*
* Initialize any WITH CHECK OPTION constraints if needed.
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index a40ddf5..e38d732 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -46,7 +46,7 @@ typedef struct TriggerData
* The state for capturing old and new tuples into transition tables for a
* single ModifyTable node (or other operation source, e.g. copy.c).
*
- * This is per-caller to avoid conflicts in setting tcs_map or
+ * This is per-caller to avoid conflicts in setting
* tcs_original_insert_tuple. Note, however, that the pointed-to
* private data may be shared across multiple callers.
*/
@@ -66,14 +66,6 @@ typedef struct TransitionCaptureState
bool tcs_insert_new_table;
/*
- * For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the
- * new and old tuples from a child table's format to the format of the
- * relation named in a query so that it is compatible with the transition
- * tuplestores. The caller must store the conversion map here if so.
- */
- TupleConversionMap *tcs_map;
-
- /*
* For INSERT and COPY, it would be wasteful to convert tuples from child
* format to parent format after they have already been converted in the
* opposite direction during routing. In that case we bypass conversion
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0b42dd6..7351c57 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -491,6 +491,14 @@ typedef struct ResultRelInfo
/* For use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
+
+ /*
+ * Map to convert child result relation tuples to the format of the
+ * table actually mentioned in the query (called "root"). Set only
+ * if either transition tuple capture or update partition row
+ * movement is active.
+ */
+ TupleConversionMap *ri_ChildToRootMap;
} ResultRelInfo;
/* ----------------
@@ -1194,9 +1202,6 @@ typedef struct ModifyTableState
/* controls transition table population for INSERT...ON CONFLICT UPDATE */
struct TransitionCaptureState *mt_oc_transition_capture;
-
- /* Per plan map for tuple conversion from child to root */
- TupleConversionMap **mt_per_subplan_tupconv_maps;
} ModifyTableState;
/* ----------------
--
1.8.3.1
Attached updated patches based on recent the discussion at:
* Re: partition routing layering in nodeModifyTable.c *
/messages/by-id/CA+HiwqHpmMjenQqNpMHrhg3DRhqqQfby2RCT1HWVwMin3_5vMA@mail.gmail.com
0001 adjusts how ForeignScanState.resultRelInfo is initialized for use
by direct modify operations.
0002 refactors ResultRelInfo initialization do be don lazily on first use
I call these v6, because the last version posted on this thread was
v5, even though it went through a couple of iterations on the above
thread. Sorry about the confusion.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v6-0001-Call-BeginDirectModify-from-ExecInitModifyTable.patchapplication/octet-stream; name=v6-0001-Call-BeginDirectModify-from-ExecInitModifyTable.patchDownload
From c86c7e7fb5112ae7e95704ae8f687f50f78da29c Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v2 1/2] Call BeginDirectModify from ExecInitModifyTable
This allows ModifyTable to directly install the target foreign
table's ResultRelInfo into the ForeignScanState node that will be
used to perform the "direct modify" operation rather than have
ExecInitForeignScan() do it by getting it via es_result_relations.
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily, whereby accessing a given target
table's ResultRelInfo directly through es_result_relations while
the ModifyTable is executing will become a deprecated pattern.
---
src/backend/executor/nodeForeignscan.c | 15 ++++-----------
src/backend/executor/nodeModifyTable.c | 32 +++++++++++++++++++++++---------
2 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0b20f94..7101c68 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -215,24 +215,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, it's the
+ * enclosing ModifyTable node's job to call the FDW after setting up the
+ * target foreign table's ResultRelInfo.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7..05e68ef 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2306,17 +2306,31 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
table_slot_callbacks(resultRelInfo->ri_RelationDesc));
/* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ if (resultRelInfo->ri_FdwRoutine != NULL)
{
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
+ if (resultRelInfo->ri_usesFdwDirectModify)
+ {
+ ForeignScanState *fscan = (ForeignScanState *) mtstate->mt_plans[i];
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- i,
- eflags);
+ /*
+ * For the FDW's convenience, set the ForeignScanState node's
+ * ResultRelInfo to let the FDW know which result relation it
+ * is going to work with.
+ */
+ Assert(IsA(fscan, ForeignScanState));
+ fscan->resultRelInfo = resultRelInfo;
+ resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
+ }
+ else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ i,
+ eflags);
+ }
}
/*
--
1.8.3.1
v6-0002-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v6-0002-Initialize-result-relation-information-lazily.patchDownload
From 0a19e71adba1e97f5150229e576e3f93eb2db0de Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v2 2/2] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
Also, extend this lazy initialization approach to some of the
individual fields of ResultRelInfo such that even for the result
relations that are initialized, those fields are only initialized on
first access. While no performance improvement is to be expected
there, it can lead to a simpler initialization logic of the
ResultRelInfo itself, because the conditions for whether a given
field is needed or not tends to look confusing. One side-effect
of this is that any "SubPlans" referenced in the expressions of
those fields are also lazily initialized and hence changes the
output of EXPLAIN (without ANALYZE) in some regression tests.
Another unrelated regression test output change is in update.out,
which is caused by deferred initialization of PartitionTupleRouting
for update tuple routing. Whereas previously a partition constraint
violation error would be reported as occurring on a leaf partition,
due to the aforementioned change, it is now shown as occurring on
the query's target relation, which is valid because it is really
that table's (which is a sub-partitioned table) partition constraint
that is actually violated in the affected test cases.
---
src/backend/commands/explain.c | 6 +-
src/backend/executor/execMain.c | 7 +
src/backend/executor/execPartition.c | 116 ++-
src/backend/executor/nodeModifyTable.c | 1123 ++++++++++++++-----------
src/include/executor/nodeModifyTable.h | 1 +
src/include/nodes/execnodes.h | 2 +
src/test/regress/expected/insert_conflict.out | 5 +-
src/test/regress/expected/updatable_views.out | 18 +-
src/test/regress/expected/update.out | 12 +-
9 files changed, 737 insertions(+), 553 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43f9b01..edd79d7 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,7 +18,9 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
#include "nodes/extensible.h"
@@ -3678,14 +3680,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ ExecGetResultRelation(mtstate, 0)->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..f484e6a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1236,6 +1236,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_GeneratedExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
+ resultRelInfo->ri_junkFilterValid = false;;
resultRelInfo->ri_projectReturning = NULL;
resultRelInfo->ri_onConflictArbiterIndexes = NIL;
resultRelInfo->ri_onConflict = NULL;
@@ -1247,6 +1248,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1440,6 +1442,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..8265db2 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -508,6 +496,41 @@ ExecFindPartition(ModifyTableState *mtstate,
}
/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ {
+ result = ExecGetResultRelation(mtstate, elem->index);
+
+ /*
+ * This is required in order to convert the partition's tuple to be
+ * compatible with the root partitioned table's tuple descriptor. When
+ * generating the per-subplan result rels, this was not set.
+ */
+ result->ri_PartitionRoot = proute->partition_root;
+ }
+
+ return result;
+}
+
+/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
* partition Oid. We also populate the subplan ResultRelInfo with an
@@ -517,9 +540,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -530,26 +557,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -570,7 +597,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -606,19 +634,26 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo = ExecGetResultRelation(mtstate, 0);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -682,7 +717,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -741,7 +775,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -916,9 +949,14 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* from partition's rowtype to the root partition table's.
*/
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
+ {
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
+ /* First time creating the map for this result relation. */
+ Assert(!leaf_part_rri->ri_ChildToRootMapValid);
+ leaf_part_rri->ri_ChildToRootMapValid = true;
+ }
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 05e68ef..db24dff 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -144,10 +144,41 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
}
/*
+ * Initialize ri_returningList and ri_projectReturning for RETURNING
+ */
+static void
+InitReturningProjection(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+{
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ int whichrel = resultRelInfo - mtstate->resultRelInfo;
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ /* Must not do this a second time! */
+ Assert(resultRelInfo->ri_returningList == NIL &&
+ resultRelInfo->ri_projectReturning == NULL);
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+}
+
+/*
* ExecProcessReturning --- evaluate a RETURNING list
*
* resultRelInfo: current result rel
- * tupleSlot: slot holding tuple actually inserted/updated/deleted
+ * tupleSlot: slot holding tuple actually inserted/updated or NULL for delete
+ * tupleid, oldtuple: when called for delete, one of these can be used to
+ * fill the RETURNING slot for the relation
* planSlot: slot holding tuple returned by top subplan node
*
* Note: If tupleSlot is NULL, the FDW should have already provided econtext's
@@ -156,12 +187,50 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
* Returns a slot holding the result tuple
*/
static TupleTableSlot *
-ExecProcessReturning(ResultRelInfo *resultRelInfo,
+ExecProcessReturning(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
TupleTableSlot *tupleSlot,
+ ItemPointer tupleid, HeapTuple oldtuple,
TupleTableSlot *planSlot)
{
- ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
- ExprContext *econtext = projectReturning->pi_exprContext;
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ProjectionInfo *projectReturning;
+ ExprContext *econtext;
+ bool clearTupleSlot = false;
+ TupleTableSlot *result;
+
+ if (plan->returningLists == NIL)
+ return NULL;
+
+ if (resultRelInfo->ri_returningList == NIL)
+ InitReturningProjection(mtstate, resultRelInfo);
+
+ projectReturning = resultRelInfo->ri_projectReturning;
+ econtext = projectReturning->pi_exprContext;
+
+ /*
+ * Fill tupleSlot with provided tuple or after fetching the tuple with
+ * provided tupleid.
+ */
+ if (tupleSlot == NULL && resultRelInfo->ri_FdwRoutine == NULL)
+ {
+ /* FDW must have provided a slot containing the deleted row */
+ Assert(resultRelInfo->ri_FdwRoutine == NULL);
+ tupleSlot = ExecGetReturningSlot(estate, resultRelInfo);
+ if (oldtuple != NULL)
+ {
+ ExecForceStoreHeapTuple(oldtuple, tupleSlot, false);
+ }
+ else
+ {
+ if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
+ tupleid, SnapshotAny,
+ tupleSlot))
+ elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+ }
+ clearTupleSlot = true;
+ }
/* Make tuple and any needed join variables available to ExecProject */
if (tupleSlot)
@@ -176,7 +245,392 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
RelationGetRelid(resultRelInfo->ri_RelationDesc);
/* Compute the RETURNING expressions */
- return ExecProject(projectReturning);
+ result = ExecProject(projectReturning);
+
+ if (clearTupleSlot)
+ ExecClearTuple(tupleSlot);
+
+ return result;
+}
+
+/*
+ * Perform WITH CHECK OPTIONS check, if any.
+ */
+static void
+ExecProcessWithCheckOptions(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot, WCOKind wco_kind)
+{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ EState *estate = mtstate->ps.state;
+
+ if (node->withCheckOptionLists == NIL)
+ return;
+
+ /* Initilize expression state if not already done. */
+ if (resultRelInfo->ri_WithCheckOptions == NIL)
+ {
+ int whichrel = resultRelInfo - mtstate->resultRelInfo;
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ wcoList = (List *) list_nth(node->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /*
+ * ExecWithCheckOptions() will skip any WCOs which are not of the kind
+ * we are looking for at this point.
+ */
+ ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
+}
+
+/*
+ * Return the list of arbiter indexes to be used for ON CONFLICT processing
+ * on given result relation, fetching it from the plan if not already done.
+ */
+static List *
+GetOnConflictArbiterIndexes(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+{
+ if (resultRelInfo->ri_onConflictArbiterIndexes == NIL)
+ {
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+ }
+
+ return resultRelInfo->ri_onConflictArbiterIndexes;
+}
+
+/*
+ * Initialize target list, projection and qual for ON CONFLICT DO UPDATE.
+ */
+static void
+InitOnConflictState(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+{
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ EState *estate = mtstate->ps.state;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+ ExprContext *econtext;
+
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a slot
+ * of the table's type here, because the slot will be used to insert
+ * into the table, and for RETURNING processing - which may access
+ * system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+}
+
+/*
+ * Initialize ri_junkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the tlist.
+ * UPDATE and DELETE always need a filter, since there's always at least one
+ * junk attribute present --- no need to look first. Typically, this will be
+ * a 'ctid' or 'wholerow' attribute, but in the case of a foreign data wrapper
+ * it might be a set of junk attributes sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk filter.
+ * Note multiple rels are only possible for UPDATE/DELETE, so we can't be
+ * fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an INSERT or
+ * UPDATE matches the target table(s).
+ */
+static void
+InitJunkFilter(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
+{
+ EState *estate = mtstate->ps.state;
+ CmdType operation = mtstate->operation;
+ Plan *subplan = mtstate->mt_plans[mtstate->mt_whichplan]->plan;
+ ListCell *l;
+ bool junk_filter_needed = false;
+
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ /* Must not do this a second time! */
+ Assert(resultRelInfo->ri_junkFilter == NULL);
+ resultRelInfo->ri_junkFilter = j;
+ resultRelInfo->ri_junkFilterValid = true;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+}
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+static TupleConversionMap *
+GetChildToRootMap(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
+
+/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ /* Nope, so initialize. */
+ if (resultRelInfo == NULL)
+ {
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (resultRelInfo->ri_FdwRoutine != NULL)
+ {
+ if (resultRelInfo->ri_usesFdwDirectModify)
+ {
+ ForeignScanState *fscan = (ForeignScanState *) mtstate->mt_plans[whichrel];
+
+ /*
+ * For the FDW's convenience, set the ForeignScanState node's
+ * ResultRelInfo to let the FDW know which result relation it
+ * is going to work with.
+ */
+ Assert(IsA(fscan, ForeignScanState));
+ fscan->resultRelInfo = resultRelInfo;
+ resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
+ }
+ else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+ }
+
+ /*
+ * If transition tuples will be captured, initialize a map to convert
+ * child tuples into the format of the table mentioned in the query
+ * (root relation), because the transition tuple store can only store
+ * tuples in the root table format. However for INSERT, the map is
+ * only initialized for a given partition when the partition itself is
+ * first initialized by ExecFindPartition. Also, this map is also
+ * needed if an UPDATE ends up having to move tuples across
+ * partitions, because in that case the child tuple to be moved first
+ * needs to be converted into the root table's format. In that case,
+ * we use GetChildToRootMap() to either create one from scratch if
+ * we didn't already create it here.
+ *
+ * Note: We cannot always initialize this map lazily, that is, use
+ * GetChildToRootMap(), because AfterTriggerSaveEvent(), which needs
+ * the map, doesn't have access to the "target" relation that is
+ * needed to create the map.
+ */
+ if (mtstate->mt_transition_capture && operation != CMD_INSERT)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ /* First time creating the map for this result relation. */
+ Assert(!resultRelInfo->ri_ChildToRootMapValid);
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
}
/*
@@ -398,12 +852,27 @@ ExecInsert(ModifyTableState *mtstate,
{
ResultRelInfo *partRelInfo;
+ /*
+ * ExecInitPartitionInfo() expects that the root parent's ri_onConflict
+ * is initialized. XXX maybe it shouldn't?
+ */
+ if (onconflict != ONCONFLICT_NONE &&
+ resultRelInfo->ri_onConflict == NULL)
+ {
+ (void) GetOnConflictArbiterIndexes(mtstate, resultRelInfo);
+ if (onconflict == ONCONFLICT_UPDATE)
+ InitOnConflictState(mtstate, resultRelInfo);
+ }
+
slot = ExecPrepareTupleRouting(mtstate, estate, proute,
resultRelInfo, slot,
&partRelInfo);
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -489,12 +958,7 @@ ExecInsert(ModifyTableState *mtstate,
wco_kind = (mtstate->operation == CMD_UPDATE) ?
WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
- /*
- * ExecWithCheckOptions() will skip any WCOs which are not of the kind
- * we are looking for at this point.
- */
- if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
+ ExecProcessWithCheckOptions(mtstate, resultRelInfo, slot, wco_kind);
/*
* Check the constraints of the tuple.
@@ -521,7 +985,8 @@ ExecInsert(ModifyTableState *mtstate,
bool specConflict;
List *arbiterIndexes;
- arbiterIndexes = resultRelInfo->ri_onConflictArbiterIndexes;
+ arbiterIndexes = GetOnConflictArbiterIndexes(mtstate,
+ resultRelInfo);
/*
* Do a non-conclusive check for conflicts first.
@@ -691,12 +1156,11 @@ ExecInsert(ModifyTableState *mtstate,
* ExecWithCheckOptions() will skip any WCOs which are not of the kind we
* are looking for at this point.
*/
- if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecProcessWithCheckOptions(mtstate, resultRelInfo, slot, WCO_VIEW_CHECK);
/* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- result = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ result = ExecProcessReturning(mtstate, resultRelInfo, slot, NULL, NULL,
+ planSlot);
return result;
}
@@ -1011,45 +1475,23 @@ ldelete:;
ar_delete_trig_tcs);
/* Process RETURNING if present and if requested */
- if (processReturning && resultRelInfo->ri_projectReturning)
+ if (processReturning)
{
- /*
- * We have to put the target tuple into a slot, which means first we
- * gotta fetch it. We can use the trigger tuple slot.
- */
- TupleTableSlot *rslot;
-
- if (resultRelInfo->ri_FdwRoutine)
- {
- /* FDW must have provided a slot containing the deleted row */
- Assert(!TupIsNull(slot));
- }
- else
- {
- slot = ExecGetReturningSlot(estate, resultRelInfo);
- if (oldtuple != NULL)
- {
- ExecForceStoreHeapTuple(oldtuple, slot, false);
- }
- else
- {
- if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid,
- SnapshotAny, slot))
- elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
- }
- }
-
- rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ TupleTableSlot *rslot = ExecProcessReturning(mtstate, resultRelInfo,
+ slot, tupleid, oldtuple,
+ planSlot);
/*
* Before releasing the target tuple again, make sure rslot has a
* local copy of any pass-by-reference values.
*/
- ExecMaterializeSlot(rslot);
-
- ExecClearTuple(slot);
-
- return rslot;
+ if (rslot)
+ {
+ ExecMaterializeSlot(rslot);
+ if (slot)
+ ExecClearTuple(slot);
+ return rslot;
+ }
}
return NULL;
@@ -1082,7 +1524,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;
@@ -1101,13 +1542,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, mtstate, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1161,7 +1616,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 = GetChildToRootMap(mtstate, resultRelInfo);
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1226,6 +1681,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1318,16 +1776,9 @@ lreplace:;
resultRelationDesc->rd_rel->relispartition &&
!ExecPartitionCheck(resultRelInfo, slot, estate, false);
- if (!partition_constraint_failed &&
- resultRelInfo->ri_WithCheckOptions != NIL)
- {
- /*
- * ExecWithCheckOptions() will skip any WCOs which are not of the
- * kind we are looking for at this point.
- */
- ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
- resultRelInfo, slot, estate);
- }
+ if (!partition_constraint_failed)
+ ExecProcessWithCheckOptions(mtstate, resultRelInfo, slot,
+ WCO_RLS_UPDATE_CHECK);
/*
* If a partition check failed, try to move the row into the right
@@ -1340,6 +1791,13 @@ lreplace:;
bool retry;
/*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
+ /*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
* root table, which will re-route it to the correct partition.
@@ -1535,18 +1993,12 @@ lreplace:;
* required to do this after testing all constraints and uniqueness
* violations per the SQL spec, so we do it after actually updating the
* record in the heap and all indexes.
- *
- * ExecWithCheckOptions() will skip any WCOs which are not of the kind we
- * are looking for at this point.
*/
- if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecProcessWithCheckOptions(mtstate, resultRelInfo, slot, WCO_VIEW_CHECK);
/* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- return ExecProcessReturning(resultRelInfo, slot, planSlot);
-
- return NULL;
+ return ExecProcessReturning(mtstate, resultRelInfo, slot, NULL, NULL,
+ planSlot);
}
/*
@@ -1570,10 +2022,10 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
bool canSetTag,
TupleTableSlot **returning)
{
- ExprContext *econtext = mtstate->ps.ps_ExprContext;
+ ExprContext *econtext;
Relation relation = resultRelInfo->ri_RelationDesc;
- ExprState *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
- TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing;
+ ExprState *onConflictSetWhere;
+ TupleTableSlot *existing;
TM_FailureData tmfd;
LockTupleMode lockmode;
TM_Result test;
@@ -1581,6 +2033,13 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
TransactionId xmin;
bool isnull;
+ if (resultRelInfo->ri_onConflict == NULL)
+ InitOnConflictState(mtstate, resultRelInfo);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
+ existing = resultRelInfo->ri_onConflict->oc_Existing;
+
/* Determine lock mode to use */
lockmode = ExecUpdateLockMode(estate, resultRelInfo);
@@ -1719,27 +2178,23 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
return true; /* done with the tuple */
}
- if (resultRelInfo->ri_WithCheckOptions != NIL)
- {
- /*
- * Check target's existing tuple against UPDATE-applicable USING
- * security barrier quals (if any), enforced here as RLS checks/WCOs.
- *
- * The rewriter creates UPDATE RLS checks/WCOs for UPDATE security
- * quals, and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK,
- * but that's almost the extent of its special handling for ON
- * CONFLICT DO UPDATE.
- *
- * The rewriter will also have associated UPDATE applicable straight
- * RLS checks/WCOs for the benefit of the ExecUpdate() call that
- * follows. INSERTs and UPDATEs naturally have mutually exclusive WCO
- * kinds, so there is no danger of spurious over-enforcement in the
- * INSERT or UPDATE path.
- */
- ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
- existing,
- mtstate->ps.state);
- }
+ /*
+ * Check target's existing tuple against UPDATE-applicable USING
+ * security barrier quals (if any), enforced here as RLS checks/WCOs.
+ *
+ * The rewriter creates UPDATE RLS checks/WCOs for UPDATE security
+ * quals, and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK,
+ * but that's almost the extent of its special handling for ON
+ * CONFLICT DO UPDATE.
+ *
+ * The rewriter will also have associated UPDATE applicable straight
+ * RLS checks/WCOs for the benefit of the ExecUpdate() call that
+ * follows. INSERTs and UPDATEs naturally have mutually exclusive WCO
+ * kinds, so there is no danger of spurious over-enforcement in the
+ * INSERT or UPDATE path.
+ */
+ ExecProcessWithCheckOptions(mtstate, resultRelInfo, existing,
+ WCO_RLS_CONFLICT_CHECK);
/* Project the new tuple version */
ExecProject(resultRelInfo->ri_onConflict->oc_ProjInfo);
@@ -1929,11 +2384,12 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -1974,9 +2430,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -2000,17 +2454,37 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans) &&
+ resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+
+ /*
+ * Must make sure to initialize the RETURNING projection as well,
+ * because some FDWs rely on accessing ri_projectReturning to
+ * set its "scan" tuple to use below for computing the actual
+ * RETURNING targetlist.
+ */
+ if (plan->returningLists && resultRelInfo->ri_returningList == NIL)
+ InitReturningProjection(node, resultRelInfo);
+ }
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2020,8 +2494,25 @@ ExecModifyTable(PlanState *pstate)
}
/*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+ if (!resultRelInfo->ri_junkFilterValid)
+ InitJunkFilter(node, resultRelInfo);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
+ /*
* Ensure input tuple is the right format for the target relation.
*/
+ if (node->mt_scans[node->mt_whichplan] == NULL)
+ node->mt_scans[node->mt_whichplan] =
+ ExecInitExtraTupleSlot(node->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
{
ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot);
@@ -2042,7 +2533,8 @@ ExecModifyTable(PlanState *pstate)
* ExecProcessReturning by IterateDirectModify, so no need to
* provide it here.
*/
- slot = ExecProcessReturning(resultRelInfo, NULL, planSlot);
+ slot = ExecProcessReturning(node, resultRelInfo, NULL, NULL, NULL,
+ planSlot);
return slot;
}
@@ -2175,13 +2667,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2198,7 +2687,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
@@ -2225,13 +2727,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
else
{
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2244,177 +2750,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
ExecSetupTransitionCaptureState(mtstate, estate);
- /*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (resultRelInfo->ri_FdwRoutine != NULL)
- {
- if (resultRelInfo->ri_usesFdwDirectModify)
- {
- ForeignScanState *fscan = (ForeignScanState *) mtstate->mt_plans[i];
-
- /*
- * For the FDW's convenience, set the ForeignScanState node's
- * ResultRelInfo to let the FDW know which result relation it
- * is going to work with.
- */
- Assert(IsA(fscan, ForeignScanState));
- fscan->resultRelInfo = resultRelInfo;
- resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
- }
- else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
- /* Get the 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.
- */
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- (operation == CMD_INSERT || update_tuple_routing_needed))
- 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
+ /* Initialize some global state for RETURNING projections. */
if (node->returningLists)
{
- TupleTableSlot *slot;
- ExprContext *econtext;
-
/*
* Initialize result tuple slot and assign its rowtype using the first
* RETURNING list. We assume the rest will look the same.
@@ -2423,27 +2761,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
/* Set up a slot for the output of the RETURNING projection(s) */
ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
/* Need an econtext too */
if (mtstate->ps.ps_ExprContext == NULL)
ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
}
else
{
@@ -2457,67 +2778,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->ps.ps_ExprContext = NULL;
}
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+ /* Get the target relation */
+ rel = mtstate->rootResultRelInfo->ri_RelationDesc;
/*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
+ * Build state for tuple routing if it's an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see ExecCrossPartitionUpdate().
*/
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ operation == CMD_INSERT)
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, mtstate, rel);
/*
* If we have any secondary relations in an UPDATE or DELETE, they need to
@@ -2555,121 +2827,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2699,20 +2856,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 46a2dc9..9ae7e40 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,6 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel);
#endif /* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6c0a7d6..f2f4bed 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -463,6 +463,7 @@ typedef struct ResultRelInfo
/* for removing junk attributes from tuples */
JunkFilter *ri_junkFilter;
+ bool ri_junkFilterValid; /* has the filter been initialized? */
/* list of RETURNING expressions */
List *ri_returningList;
@@ -497,6 +498,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index ff157ce..74cd7e2 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -52,10 +52,7 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con
Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key
Conflict Filter: (SubPlan 1)
-> Result
- SubPlan 1
- -> Index Only Scan using both_index_expr_key on insertconflicttest ii
- Index Cond: (key = excluded.key)
-(8 rows)
+(5 rows)
-- Neither collation nor operator class specifications are required --
-- supplying them merely *limits* matches to indexes with matching opclasses
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index caed1c1..d8d2a3d 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1862,28 +1862,22 @@ UPDATE rw_view1 SET a = a + 5; -- should fail
ERROR: new row violates check option for view "rw_view1"
DETAIL: Failing row contains (15).
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
- QUERY PLAN
----------------------------------------------------------
+ QUERY PLAN
+----------------------
Insert on base_tbl b
-> Result
- SubPlan 1
- -> Index Only Scan using ref_tbl_pkey on ref_tbl r
- Index Cond: (a = b.a)
-(5 rows)
+(2 rows)
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
- QUERY PLAN
------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------
Update on base_tbl b
-> Hash Join
Hash Cond: (b.a = r.a)
-> Seq Scan on base_tbl b
-> Hash
-> Seq Scan on ref_tbl r
- SubPlan 1
- -> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
- Index Cond: (a = b.a)
-(9 rows)
+(6 rows)
DROP TABLE base_tbl, ref_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..0ad0d1a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
1.8.3.1
On 30/10/2020 08:13, Amit Langote wrote:
/*
* Perform WITH CHECK OPTIONS check, if any.
*/
static void
ExecProcessWithCheckOptions(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, WCOKind wco_kind)
{
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
EState *estate = mtstate->ps.state;if (node->withCheckOptionLists == NIL)
return;/* Initialize expression state if not already done. */
if (resultRelInfo->ri_WithCheckOptions == NIL)
{
int whichrel = resultRelInfo - mtstate->resultRelInfo;
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
wcoList = (List *) list_nth(node->withCheckOptionLists, whichrel);
foreach(ll, wcoList)
{
WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
&mtstate->ps);wcoExprs = lappend(wcoExprs, wcoExpr);
}resultRelInfo->ri_WithCheckOptions = wcoList;
resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
}/*
* ExecWithCheckOptions() will skip any WCOs which are not of the kind
* we are looking for at this point.
*/
ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
}
Can we do this initialization in ExecGetResultRelation()? That would
seem much more straightforward. Is there any advantage to delaying it
here? And same thing with the junk filter and the RETURNING list.
(/me reads patch further) I presume that's what you referred to in the
commit message:
Also, extend this lazy initialization approach to some of the
individual fields of ResultRelInfo such that even for the result
relations that are initialized, those fields are only initialized on
first access. While no performance improvement is to be expected
there, it can lead to a simpler initialization logic of the
ResultRelInfo itself, because the conditions for whether a given
field is needed or not tends to look confusing. One side-effect
of this is that any "SubPlans" referenced in the expressions of
those fields are also lazily initialized and hence changes the
output of EXPLAIN (without ANALYZE) in some regression tests.
I'm now curious what the initialization logic would look like, if we
initialized those fields in ExecGetResultRelation(). At a quick glance
on the conditions on when those initializations are done in the patch
now, it would seem pretty straightforward. If the target list contains
any junk columns, initialize junk filter, and if
ModifyTable->returningLists is set, initialize RETURNING list. Maybe I'm
missing something.
- Heikki
On Mon, Nov 2, 2020 at 10:19 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 30/10/2020 08:13, Amit Langote wrote:
/*
* Perform WITH CHECK OPTIONS check, if any.
*/
static void
ExecProcessWithCheckOptions(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, WCOKind wco_kind)
{
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
EState *estate = mtstate->ps.state;if (node->withCheckOptionLists == NIL)
return;/* Initialize expression state if not already done. */
if (resultRelInfo->ri_WithCheckOptions == NIL)
{
int whichrel = resultRelInfo - mtstate->resultRelInfo;
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
wcoList = (List *) list_nth(node->withCheckOptionLists, whichrel);
foreach(ll, wcoList)
{
WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
&mtstate->ps);wcoExprs = lappend(wcoExprs, wcoExpr);
}resultRelInfo->ri_WithCheckOptions = wcoList;
resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
}/*
* ExecWithCheckOptions() will skip any WCOs which are not of the kind
* we are looking for at this point.
*/
ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
}Can we do this initialization in ExecGetResultRelation()? That would
seem much more straightforward. Is there any advantage to delaying it
here? And same thing with the junk filter and the RETURNING list.(/me reads patch further) I presume that's what you referred to in the
commit message:Also, extend this lazy initialization approach to some of the
individual fields of ResultRelInfo such that even for the result
relations that are initialized, those fields are only initialized on
first access. While no performance improvement is to be expected
there, it can lead to a simpler initialization logic of the
ResultRelInfo itself, because the conditions for whether a given
field is needed or not tends to look confusing. One side-effect
of this is that any "SubPlans" referenced in the expressions of
those fields are also lazily initialized and hence changes the
output of EXPLAIN (without ANALYZE) in some regression tests.I'm now curious what the initialization logic would look like, if we
initialized those fields in ExecGetResultRelation(). At a quick glance
on the conditions on when those initializations are done in the patch
now, it would seem pretty straightforward. If the target list contains
any junk columns, initialize junk filter, and if
ModifyTable->returningLists is set, initialize RETURNING list. Maybe I'm
missing something.
Yeah, it's not that complicated to initialize those things in
ExecGetResultRelation(). In fact, ExecGetResultRelation() (or its
subroutine ExecBuildResultRelation()) housed those initializations in
the earlier versions of this patch, but I changed that after our
discussion about being lazy about initializing as much stuff as we
can. Maybe I should revert that?
--
Amit Langote
EDB: http://www.enterprisedb.com
On Mon, Nov 2, 2020 at 10:53 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Mon, Nov 2, 2020 at 10:19 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
(/me reads patch further) I presume that's what you referred to in the
commit message:Also, extend this lazy initialization approach to some of the
individual fields of ResultRelInfo such that even for the result
relations that are initialized, those fields are only initialized on
first access. While no performance improvement is to be expected
there, it can lead to a simpler initialization logic of the
ResultRelInfo itself, because the conditions for whether a given
field is needed or not tends to look confusing. One side-effect
of this is that any "SubPlans" referenced in the expressions of
those fields are also lazily initialized and hence changes the
output of EXPLAIN (without ANALYZE) in some regression tests.I'm now curious what the initialization logic would look like, if we
initialized those fields in ExecGetResultRelation(). At a quick glance
on the conditions on when those initializations are done in the patch
now, it would seem pretty straightforward. If the target list contains
any junk columns, initialize junk filter, and if
ModifyTable->returningLists is set, initialize RETURNING list. Maybe I'm
missing something.Yeah, it's not that complicated to initialize those things in
ExecGetResultRelation(). In fact, ExecGetResultRelation() (or its
subroutine ExecBuildResultRelation()) housed those initializations in
the earlier versions of this patch, but I changed that after our
discussion about being lazy about initializing as much stuff as we
can. Maybe I should revert that?
Please check the attached if that looks better.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v7-0001-Call-BeginDirectModify-from-ExecInitModifyTable.patchapplication/octet-stream; name=v7-0001-Call-BeginDirectModify-from-ExecInitModifyTable.patchDownload
From 9cbf2b6c67671bfd73f1cfcb0695ddcc0f435b87 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v7 1/2] Call BeginDirectModify from ExecInitModifyTable
This allows ModifyTable to directly install the target foreign
table's ResultRelInfo into the ForeignScanState node that will be
used to perform the "direct modify" operation rather than have
ExecInitForeignScan() do it by getting it via es_result_relations.
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily, whereby accessing a given target
table's ResultRelInfo directly through es_result_relations while
the ModifyTable is executing will become a deprecated pattern.
---
src/backend/executor/nodeForeignscan.c | 15 ++++-----------
src/backend/executor/nodeModifyTable.c | 32 +++++++++++++++++++++++---------
2 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0b20f94..7101c68 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -215,24 +215,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, it's the
+ * enclosing ModifyTable node's job to call the FDW after setting up the
+ * target foreign table's ResultRelInfo.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7..05e68ef 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2306,17 +2306,31 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
table_slot_callbacks(resultRelInfo->ri_RelationDesc));
/* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ if (resultRelInfo->ri_FdwRoutine != NULL)
{
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
+ if (resultRelInfo->ri_usesFdwDirectModify)
+ {
+ ForeignScanState *fscan = (ForeignScanState *) mtstate->mt_plans[i];
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- i,
- eflags);
+ /*
+ * For the FDW's convenience, set the ForeignScanState node's
+ * ResultRelInfo to let the FDW know which result relation it
+ * is going to work with.
+ */
+ Assert(IsA(fscan, ForeignScanState));
+ fscan->resultRelInfo = resultRelInfo;
+ resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
+ }
+ else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ i,
+ eflags);
+ }
}
/*
--
1.8.3.1
v7-0002-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v7-0002-Initialize-result-relation-information-lazily.patchDownload
From d696a7e97a1d18411087851707e77441717d42cb Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v7 2/2] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also delays the initialization of
ModifyTableState.mt_partition_tuple_routing in the UPDATE case to the
first time ExecCrossPartitionUpdate() is called. That allows us to
get rid of the somewhat convoluted logic used to decide whether
ExecInitModifyTable() should initialize it. Related to that it the
lazy initialization of ri_ChildToRootMap in the ResultRelInfo of the
source partition of a tuple movement operation. Note that there is a
regression test output change in update.out resulting from this change
-- whereas previously a partition constraint violation error would be
reported as occurring on a leaf partition, it is now shown as occurring
on the query's target relation, which is valid because it is really
that table's (which is a sub-partitioned table) partition constraint
that is actually violated in the affected test cases.
While at it, also delay the opening of result relation indices,
ExecOpenIndices(), to the first time ExecInsert() or ExecUpdate() is
called.
---
src/backend/commands/explain.c | 5 +-
src/backend/executor/execMain.c | 6 +
src/backend/executor/execPartition.c | 116 +++--
src/backend/executor/nodeModifyTable.c | 900 +++++++++++++++++----------------
src/include/executor/nodeModifyTable.h | 1 +
src/include/nodes/execnodes.h | 1 +
src/test/regress/expected/update.out | 12 +-
7 files changed, 571 insertions(+), 470 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43f9b01..95c613e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -19,6 +19,7 @@
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/nodeHash.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
#include "nodes/extensible.h"
@@ -3678,14 +3679,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ ExecGetResultRelation(mtstate, 0)->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..cdea3f8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1247,6 +1247,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1440,6 +1441,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..8265db2 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -508,6 +496,41 @@ ExecFindPartition(ModifyTableState *mtstate,
}
/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ {
+ result = ExecGetResultRelation(mtstate, elem->index);
+
+ /*
+ * This is required in order to convert the partition's tuple to be
+ * compatible with the root partitioned table's tuple descriptor. When
+ * generating the per-subplan result rels, this was not set.
+ */
+ result->ri_PartitionRoot = proute->partition_root;
+ }
+
+ return result;
+}
+
+/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
* partition Oid. We also populate the subplan ResultRelInfo with an
@@ -517,9 +540,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -530,26 +557,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -570,7 +597,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -606,19 +634,26 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo = ExecGetResultRelation(mtstate, 0);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -682,7 +717,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -741,7 +775,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -916,9 +949,14 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* from partition's rowtype to the root partition table's.
*/
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
+ {
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
+ /* First time creating the map for this result relation. */
+ Assert(!leaf_part_rri->ri_ChildToRootMapValid);
+ leaf_part_rri->ri_ChildToRootMapValid = true;
+ }
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 05e68ef..fbb6102 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -180,6 +180,359 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
}
/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+static TupleConversionMap *
+GetChildToRootMap(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
+
+/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ /* Nope, so initialize. */
+ if (resultRelInfo == NULL)
+ {
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ Plan *subplan = mtstate->mt_plans[whichrel]->plan;
+ bool junk_filter_needed = false;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (resultRelInfo->ri_FdwRoutine != NULL)
+ {
+ if (resultRelInfo->ri_usesFdwDirectModify)
+ {
+ ForeignScanState *fscan = (ForeignScanState *) subplan;
+
+ /*
+ * For the FDW's convenience, set the ForeignScanState node's
+ * ResultRelInfo to let the FDW know which result relation it
+ * is going to work with.
+ */
+ Assert(IsA(fscan, ForeignScanState));
+ fscan->resultRelInfo = resultRelInfo;
+ resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
+ }
+ else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+ }
+
+ /*
+ * If transition tuples will be captured, initialize a map to convert
+ * child tuples into the format of the table mentioned in the query
+ * (root relation), because the transition tuple store can only store
+ * tuples in the root table format. However for INSERT, the map is
+ * only initialized for a given partition when the partition itself is
+ * first initialized by ExecFindPartition. Also, this map is also
+ * needed if an UPDATE ends up having to move tuples across
+ * partitions, because in that case the child tuple to be moved first
+ * needs to be converted into the root table's format. In that case,
+ * we use GetChildToRootMap() to either create one from scratch if
+ * we didn't already create it here.
+ *
+ * Note: We cannot always initialize this map lazily, that is, use
+ * GetChildToRootMap(), because AfterTriggerSaveEvent(), which needs
+ * the map, doesn't have access to the "target" relation that is
+ * needed to create the map.
+ */
+ if (mtstate->mt_transition_capture && operation != CMD_INSERT)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ /* First time creating the map for this result relation. */
+ Assert(!resultRelInfo->ri_ChildToRootMapValid);
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize JunkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the
+ * tlist. UPDATE and DELETE always need a filter, since there's always
+ * at least one junk attribute present --- no need to look first.
+ * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+ * case of a foreign data wrapper it might be a set of junk attributes
+ * sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an
+ * INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
+/*
* ExecCheckTupleVisible -- verify tuple is visible
*
* It would not be consistent with guarantees of the higher isolation levels to
@@ -404,6 +757,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -1082,7 +1438,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;
@@ -1101,13 +1456,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, mtstate, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1161,7 +1530,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 = GetChildToRootMap(mtstate, resultRelInfo);
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1226,6 +1595,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1340,6 +1712,13 @@ lreplace:;
bool retry;
/*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
+ /*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
* root table, which will re-route it to the correct partition.
@@ -1929,11 +2308,12 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -1974,9 +2354,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -2000,17 +2378,26 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans) &&
+ resultRelInfo == NULL)
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2020,8 +2407,23 @@ ExecModifyTable(PlanState *pstate)
}
/*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
+ /*
* Ensure input tuple is the right format for the target relation.
*/
+ if (node->mt_scans[node->mt_whichplan] == NULL)
+ node->mt_scans[node->mt_whichplan] =
+ ExecInitExtraTupleSlot(node->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
{
ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot);
@@ -2175,13 +2577,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2198,11 +2597,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2212,12 +2652,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* - the root partitioned table used for tuple routing.
*
* If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * elsewhere in the plan unless if it's an INSERT and its RT index is
+ * given explicitly in node->rootRelation. Otherwise (i.e. table
+ * inheritance) the target relation is the first relation in the
+ * node->resultRelations list.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
@@ -2225,13 +2666,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
else
{
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2244,282 +2689,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
ExecSetupTransitionCaptureState(mtstate, estate);
- /*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (resultRelInfo->ri_FdwRoutine != NULL)
- {
- if (resultRelInfo->ri_usesFdwDirectModify)
- {
- ForeignScanState *fscan = (ForeignScanState *) mtstate->mt_plans[i];
-
- /*
- * For the FDW's convenience, set the ForeignScanState node's
- * ResultRelInfo to let the FDW know which result relation it
- * is going to work with.
- */
- Assert(IsA(fscan, ForeignScanState));
- fscan->resultRelInfo = resultRelInfo;
- resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
- }
- else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
/* Get the 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 an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
- /*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
* EvalPlanQual mechanism needs to be told about them. Locate the
@@ -2555,121 +2738,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2699,20 +2767,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 46a2dc9..9ae7e40 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,6 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel);
#endif /* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6c0a7d6..03a6555 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -497,6 +497,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..0ad0d1a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
1.8.3.1
On 03/11/2020 10:27, Amit Langote wrote:
Please check the attached if that looks better.
Great, thanks! Yeah, I like that much better.
This makes me a bit unhappy:
/* Also let FDWs init themselves for foreign-table result rels */
if (resultRelInfo->ri_FdwRoutine != NULL)
{
if (resultRelInfo->ri_usesFdwDirectModify)
{
ForeignScanState *fscan = (ForeignScanState *) mtstate->mt_plans[i];/*
* For the FDW's convenience, set the ForeignScanState node's
* ResultRelInfo to let the FDW know which result relation it
* is going to work with.
*/
Assert(IsA(fscan, ForeignScanState));
fscan->resultRelInfo = resultRelInfo;
resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
}
else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
{
List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
resultRelInfo,
fdw_private,
i,
eflags);
}
}
If you remember, I was unhappy with a similar assertion in the earlier
patches [1]/messages/by-id/19c23dd9-89ce-75a3-9105-5fc05a46f94a@iki.fi. I'm not sure what to do instead though. A few options:
A) We could change FDW API so that BeginDirectModify takes the same
arguments as BeginForeignModify(). That avoids the assumption that it's
a ForeignScan node, because BeginForeignModify() doesn't take
ForeignScanState as argument. That would be consistent, which is nice.
But I think we'd somehow still need to pass the ResultRelInfo to the
corresponding ForeignScan, and I'm not sure how.
B) Look up the ResultRelInfo, and call BeginDirectModify(), on the first
call to ForeignNext().
C) Accept the Assertion. And add an elog() check in the planner for that
with a proper error message.
I'm leaning towards B), but maybe there's some better solution I didn't
think of? Perhaps changing the API would make sense in any case, it is a
bit weird as it is. Backwards-incompatible API changes are not nice, but
I don't think there are many FDWs out there that implement the
DirectModify functions. And those functions are pretty tightly coupled
with the executor and ModifyTable node details anyway, so I don't feel
like we can, or need to, guarantee that they stay unchanged across major
versions.
[1]: /messages/by-id/19c23dd9-89ce-75a3-9105-5fc05a46f94a@iki.fi
/messages/by-id/19c23dd9-89ce-75a3-9105-5fc05a46f94a@iki.fi
- Heikki
On Tue, Nov 3, 2020 at 9:05 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 03/11/2020 10:27, Amit Langote wrote:
Please check the attached if that looks better.
Great, thanks! Yeah, I like that much better.
This makes me a bit unhappy:
/* Also let FDWs init themselves for foreign-table result rels */
if (resultRelInfo->ri_FdwRoutine != NULL)
{
if (resultRelInfo->ri_usesFdwDirectModify)
{
ForeignScanState *fscan = (ForeignScanState *) mtstate->mt_plans[i];/*
* For the FDW's convenience, set the ForeignScanState node's
* ResultRelInfo to let the FDW know which result relation it
* is going to work with.
*/
Assert(IsA(fscan, ForeignScanState));
fscan->resultRelInfo = resultRelInfo;
resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
}
else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
{
List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
resultRelInfo,
fdw_private,
i,
eflags);
}
}If you remember, I was unhappy with a similar assertion in the earlier
patches [1]. I'm not sure what to do instead though. A few options:A) We could change FDW API so that BeginDirectModify takes the same
arguments as BeginForeignModify(). That avoids the assumption that it's
a ForeignScan node, because BeginForeignModify() doesn't take
ForeignScanState as argument. That would be consistent, which is nice.
But I think we'd somehow still need to pass the ResultRelInfo to the
corresponding ForeignScan, and I'm not sure how.
Maybe ForeignScan doesn't need to contain any result relation info
then? ForeignScan.operation != CMD_SELECT is enough to tell it to
call IterateDirectModify() as today.
B) Look up the ResultRelInfo, and call BeginDirectModify(), on the first
call to ForeignNext().C) Accept the Assertion. And add an elog() check in the planner for that
with a proper error message.I'm leaning towards B), but maybe there's some better solution I didn't
think of? Perhaps changing the API would make sense in any case, it is a
bit weird as it is. Backwards-incompatible API changes are not nice, but
I don't think there are many FDWs out there that implement the
DirectModify functions. And those functions are pretty tightly coupled
with the executor and ModifyTable node details anyway, so I don't feel
like we can, or need to, guarantee that they stay unchanged across major
versions.
B is not too bad, but I tend to prefer doing A too.
--
Amit Langote
EDB: http://www.enterprisedb.com
On Wed, Nov 4, 2020 at 11:32 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Tue, Nov 3, 2020 at 9:05 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
A) We could change FDW API so that BeginDirectModify takes the same
arguments as BeginForeignModify(). That avoids the assumption that it's
a ForeignScan node, because BeginForeignModify() doesn't take
ForeignScanState as argument. That would be consistent, which is nice.
But I think we'd somehow still need to pass the ResultRelInfo to the
corresponding ForeignScan, and I'm not sure how.Maybe ForeignScan doesn't need to contain any result relation info
then? ForeignScan.operation != CMD_SELECT is enough to tell it to
call IterateDirectModify() as today.B) Look up the ResultRelInfo, and call BeginDirectModify(), on the first
call to ForeignNext().C) Accept the Assertion. And add an elog() check in the planner for that
with a proper error message.I'm leaning towards B), but maybe there's some better solution I didn't
think of? Perhaps changing the API would make sense in any case, it is a
bit weird as it is. Backwards-incompatible API changes are not nice, but
I don't think there are many FDWs out there that implement the
DirectModify functions. And those functions are pretty tightly coupled
with the executor and ModifyTable node details anyway, so I don't feel
like we can, or need to, guarantee that they stay unchanged across major
versions.B is not too bad, but I tend to prefer doing A too.
How about I update the 0001 patch to implement A?
--
Amit Langote
EDB: http://www.enterprisedb.com
On Wed, Nov 4, 2020 at 11:32 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Tue, Nov 3, 2020 at 9:05 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 03/11/2020 10:27, Amit Langote wrote:
Please check the attached if that looks better.
Great, thanks! Yeah, I like that much better.
This makes me a bit unhappy:
/* Also let FDWs init themselves for foreign-table result rels */
if (resultRelInfo->ri_FdwRoutine != NULL)
{
if (resultRelInfo->ri_usesFdwDirectModify)
{
ForeignScanState *fscan = (ForeignScanState *) mtstate->mt_plans[i];/*
* For the FDW's convenience, set the ForeignScanState node's
* ResultRelInfo to let the FDW know which result relation it
* is going to work with.
*/
Assert(IsA(fscan, ForeignScanState));
fscan->resultRelInfo = resultRelInfo;
resultRelInfo->ri_FdwRoutine->BeginDirectModify(fscan, eflags);
}
else if (resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
{
List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
resultRelInfo,
fdw_private,
i,
eflags);
}
}If you remember, I was unhappy with a similar assertion in the earlier
patches [1]. I'm not sure what to do instead though. A few options:A) We could change FDW API so that BeginDirectModify takes the same
arguments as BeginForeignModify(). That avoids the assumption that it's
a ForeignScan node, because BeginForeignModify() doesn't take
ForeignScanState as argument. That would be consistent, which is nice.
But I think we'd somehow still need to pass the ResultRelInfo to the
corresponding ForeignScan, and I'm not sure how.Maybe ForeignScan doesn't need to contain any result relation info
then? ForeignScan.operation != CMD_SELECT is enough to tell it to
call IterateDirectModify() as today.
Hmm, I misspoke. We do still need ForeignScanState.resultRelInfo,
because the IterateDirectModify() API uses it to return the remotely
inserted/updated/deleted tuple for the RETURNING projection performed
by ExecModifyTable().
B) Look up the ResultRelInfo, and call BeginDirectModify(), on the first
call to ForeignNext().C) Accept the Assertion. And add an elog() check in the planner for that
with a proper error message.I'm leaning towards B), but maybe there's some better solution I didn't
think of? Perhaps changing the API would make sense in any case, it is a
bit weird as it is. Backwards-incompatible API changes are not nice, but
I don't think there are many FDWs out there that implement the
DirectModify functions. And those functions are pretty tightly coupled
with the executor and ModifyTable node details anyway, so I don't feel
like we can, or need to, guarantee that they stay unchanged across major
versions.B is not too bad, but I tend to prefer doing A too.
On second thought, it seems A would amount to merely a cosmetic
adjustment of the API, nothing more. B seems to get the job done for
me and also doesn't unnecessarily break compatibility, so I've updated
0001 to implement B. Please give it a look.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v8-0001-Set-ForeignScanState.resultRelInfo-lazily.patchapplication/octet-stream; name=v8-0001-Set-ForeignScanState.resultRelInfo-lazily.patchDownload
From f0204a89327c133b37d39fefbd4ba59fb5d9756a Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v8 1/2] Set ForeignScanState.resultRelInfo lazily
Instead of doing it in ExecInitForeignScan(), do it on the first
ForeignNext() call. This also moves the BeginDirectModify() call
into ForeignNext().
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily, that is as it begins executing,
instead of in ExecInitModifyTable().
---
doc/src/sgml/fdwhandler.sgml | 7 +++---
src/backend/executor/nodeForeignscan.c | 39 ++++++++++++++++++++++++----------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 9c92934..b1d7c84 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -890,9 +890,10 @@ BeginDirectModify(ForeignScanState *node,
</programlisting>
Prepare to execute a direct modification on the remote server.
- This is called during executor startup. It should perform any
- initialization needed prior to the direct modification (that should be
- done upon the first call to <function>IterateDirectModify</function>).
+ This is called right before the first time <function>IterateDirectModify</function>
+ is called on the node. It should perform any initialization needed prior to the
+ direct modification (that should be done upon the first call to
+ <function>IterateDirectModify</function>).
The <structname>ForeignScanState</structname> node has already been created, but
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0b20f94..5259abf 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -49,7 +49,31 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
+ {
+ /*
+ * For FDW's convenience, look up the result relation info and set
+ * ForeignScanState.resultRelInfo if not already done. This is also
+ * a good time to call BeginDirectModify().
+ */
+ Assert(plan->resultRelation > 0);
+ if (node->resultRelInfo == NULL)
+ {
+ EState *estate = node->ss.ps.state;
+ ResultRelInfo *rInfo = estate->es_result_relations[plan->resultRelation - 1];
+
+ /* ExecInitModifyTable() must have initialized one already. */
+ Assert(rInfo != NULL);
+ node->resultRelInfo = rInfo;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ Assert(rInfo->ri_FdwRoutine != NULL &&
+ rInfo->ri_FdwRoutine->BeginDirectModify != NULL);
+ rInfo->ri_FdwRoutine->BeginDirectModify(node,
+ estate->es_top_eflags);
+ MemoryContextSwitchTo(oldcontext);
+ }
slot = node->fdwroutine->IterateDirectModify(node);
+ }
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@@ -215,24 +239,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, any
+ * additional initializations are performed right before calling
+ * IterateDirectModify() for the first time.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
--
1.8.3.1
v8-0002-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v8-0002-Initialize-result-relation-information-lazily.patchDownload
From 61b1b8419b97eb9317f07b548b530dcf330ede58 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v8 2/2] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also delays the initialization of
ModifyTableState.mt_partition_tuple_routing in the UPDATE case to the
first time ExecCrossPartitionUpdate() is called. That allows us to
get rid of the somewhat convoluted logic used to decide whether
ExecInitModifyTable() should initialize it. Related to that it the
lazy initialization of ri_ChildToRootMap in the ResultRelInfo of the
source partition of a tuple movement operation. Note that there is a
regression test output change in update.out resulting from this change
-- whereas previously a partition constraint violation error would be
reported as occurring on a leaf partition, it is now shown as occurring
on the query's target relation, which is valid because it is really
that table's (which is a sub-partitioned table) partition constraint
that is actually violated in the affected test cases.
While at it, also delay the opening of result relation indices,
ExecOpenIndices(), to the first time ExecInsert() or ExecUpdate() is
called.
---
src/backend/commands/explain.c | 5 +-
src/backend/executor/execMain.c | 6 +
src/backend/executor/execPartition.c | 116 +++--
src/backend/executor/nodeModifyTable.c | 877 ++++++++++++++++++---------------
src/include/executor/nodeModifyTable.h | 1 +
src/include/nodes/execnodes.h | 1 +
src/test/regress/expected/update.out | 12 +-
7 files changed, 562 insertions(+), 456 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43f9b01..95c613e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -19,6 +19,7 @@
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/nodeHash.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
#include "nodes/extensible.h"
@@ -3678,14 +3679,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ ExecGetResultRelation(mtstate, 0)->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..cdea3f8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1247,6 +1247,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1440,6 +1441,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..8265db2 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -508,6 +496,41 @@ ExecFindPartition(ModifyTableState *mtstate,
}
/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ {
+ result = ExecGetResultRelation(mtstate, elem->index);
+
+ /*
+ * This is required in order to convert the partition's tuple to be
+ * compatible with the root partitioned table's tuple descriptor. When
+ * generating the per-subplan result rels, this was not set.
+ */
+ result->ri_PartitionRoot = proute->partition_root;
+ }
+
+ return result;
+}
+
+/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
* partition Oid. We also populate the subplan ResultRelInfo with an
@@ -517,9 +540,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -530,26 +557,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -570,7 +597,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -606,19 +634,26 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo = ExecGetResultRelation(mtstate, 0);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -682,7 +717,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -741,7 +775,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -916,9 +949,14 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* from partition's rowtype to the root partition table's.
*/
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
+ {
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
+ /* First time creating the map for this result relation. */
+ Assert(!leaf_part_rri->ri_ChildToRootMapValid);
+ leaf_part_rri->ri_ChildToRootMapValid = true;
+ }
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7..0e4a76b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -180,6 +180,355 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
}
/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+static TupleConversionMap *
+GetChildToRootMap(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
+
+/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ /* Nope, so initialize. */
+ if (resultRelInfo == NULL)
+ {
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ PlanState *subplanstate = mtstate->mt_plans[whichrel];
+ Plan *subplan = subplanstate->plan;
+ bool junk_filter_needed = false;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+
+ /*
+ * If transition tuples will be captured, initialize a map to convert
+ * child tuples into the format of the table mentioned in the query
+ * (root relation), because the transition tuple store can only store
+ * tuples in the root table format. However for INSERT, the map is
+ * only initialized for a given partition when the partition itself is
+ * first initialized by ExecFindPartition. Also, this map is also
+ * needed if an UPDATE ends up having to move tuples across
+ * partitions, because in that case the child tuple to be moved first
+ * needs to be converted into the root table's format. In that case,
+ * we use GetChildToRootMap() to either create one from scratch if
+ * we didn't already create it here.
+ *
+ * Note: We cannot always initialize this map lazily, that is, use
+ * GetChildToRootMap(), because AfterTriggerSaveEvent(), which needs
+ * the map, doesn't have access to the "target" relation that is
+ * needed to create the map.
+ */
+ if (mtstate->mt_transition_capture && operation != CMD_INSERT)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ /* First time creating the map for this result relation. */
+ Assert(!resultRelInfo->ri_ChildToRootMapValid);
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize JunkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the
+ * tlist. UPDATE and DELETE always need a filter, since there's always
+ * at least one junk attribute present --- no need to look first.
+ * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+ * case of a foreign data wrapper it might be a set of junk attributes
+ * sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an
+ * INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * While at it, also initialize a result relation specific slot that
+ * will be used to copy the plan's output tuples into.
+ */
+ Assert(mtstate->mt_scans[whichrel] == NULL);
+ mtstate->mt_scans[whichrel] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
+/*
* ExecCheckTupleVisible -- verify tuple is visible
*
* It would not be consistent with guarantees of the higher isolation levels to
@@ -404,6 +753,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -1082,7 +1434,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;
@@ -1101,13 +1452,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, mtstate, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1161,7 +1526,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 = GetChildToRootMap(mtstate, resultRelInfo);
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1226,6 +1591,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1340,6 +1708,13 @@ lreplace:;
bool retry;
/*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
+ /*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
* root table, which will re-route it to the correct partition.
@@ -1929,11 +2304,12 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -1974,9 +2350,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -2000,17 +2374,26 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans) &&
+ resultRelInfo == NULL)
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2020,6 +2403,16 @@ ExecModifyTable(PlanState *pstate)
}
/*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
+ /*
* Ensure input tuple is the right format for the target relation.
*/
if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
@@ -2175,13 +2568,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2198,11 +2588,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2212,12 +2643,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* - the root partitioned table used for tuple routing.
*
* If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * elsewhere in the plan unless if it's an INSERT and its RT index is
+ * given explicitly in node->rootRelation. Otherwise (i.e. table
+ * inheritance) the target relation is the first relation in the
+ * node->resultRelations list.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
@@ -2225,13 +2657,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
else
{
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2244,268 +2680,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
ExecSetupTransitionCaptureState(mtstate, estate);
- /*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
/* Get the 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 an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
- /*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
* EvalPlanQual mechanism needs to be told about them. Locate the
@@ -2541,121 +2729,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2685,20 +2758,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 46a2dc9..9ae7e40 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,6 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel);
#endif /* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6c0a7d6..03a6555 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -497,6 +497,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..0ad0d1a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
1.8.3.1
On 10/11/2020 13:12, Amit Langote wrote:
On Wed, Nov 4, 2020 at 11:32 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Tue, Nov 3, 2020 at 9:05 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
A) We could change FDW API so that BeginDirectModify takes the same
arguments as BeginForeignModify(). That avoids the assumption that it's
a ForeignScan node, because BeginForeignModify() doesn't take
ForeignScanState as argument. That would be consistent, which is nice.
But I think we'd somehow still need to pass the ResultRelInfo to the
corresponding ForeignScan, and I'm not sure how.Maybe ForeignScan doesn't need to contain any result relation info
then? ForeignScan.operation != CMD_SELECT is enough to tell it to
call IterateDirectModify() as today.Hmm, I misspoke. We do still need ForeignScanState.resultRelInfo,
because the IterateDirectModify() API uses it to return the remotely
inserted/updated/deleted tuple for the RETURNING projection performed
by ExecModifyTable().B) Look up the ResultRelInfo, and call BeginDirectModify(), on the first
call to ForeignNext().C) Accept the Assertion. And add an elog() check in the planner for that
with a proper error message.I'm leaning towards B), but maybe there's some better solution I didn't
think of? Perhaps changing the API would make sense in any case, it is a
bit weird as it is. Backwards-incompatible API changes are not nice, but
I don't think there are many FDWs out there that implement the
DirectModify functions. And those functions are pretty tightly coupled
with the executor and ModifyTable node details anyway, so I don't feel
like we can, or need to, guarantee that they stay unchanged across major
versions.B is not too bad, but I tend to prefer doing A too.
On second thought, it seems A would amount to merely a cosmetic
adjustment of the API, nothing more. B seems to get the job done for
me and also doesn't unnecessarily break compatibility, so I've updated
0001 to implement B. Please give it a look.
Looks good at a quick glance. It is a small API break that
BeginDirectModify() is now called during execution, not at executor
startup, but I don't think that's going to break FDWs in practice. One
could argue, though, that if we're going to change the API, we should do
it more loudly. So changing the arguments might be a good thing.
The BeginDirectModify() and BeginForeignModify() interfaces are
inconsistent, but that's not this patch's fault. I wonder if we could
move the call to BeginForeignModify() also to ForeignNext(), though? And
BeginForeignScan() too, while we're at it.
Overall, this is probably fine as it is though. I'll review more
thorougly tomorrow.
- Heikki
On 10/11/2020 17:32, Heikki Linnakangas wrote:
On 10/11/2020 13:12, Amit Langote wrote:
On second thought, it seems A would amount to merely a cosmetic
adjustment of the API, nothing more. B seems to get the job done for
me and also doesn't unnecessarily break compatibility, so I've updated
0001 to implement B. Please give it a look.Looks good at a quick glance. It is a small API break that
BeginDirectModify() is now called during execution, not at executor
startup, but I don't think that's going to break FDWs in practice. One
could argue, though, that if we're going to change the API, we should do
it more loudly. So changing the arguments might be a good thing.The BeginDirectModify() and BeginForeignModify() interfaces are
inconsistent, but that's not this patch's fault. I wonder if we could
move the call to BeginForeignModify() also to ForeignNext(), though? And
BeginForeignScan() too, while we're at it.
With these patches, BeginForeignModify() and BeginDirectModify() are
both called during execution, before the first
IterateForeignScan/IterateDirectModify call. The documentation for
BeginForeignModify() needs to be updated, it still claims that it's run
at executor startup, but that's not true after these patches. So that
needs to be updated.
I think that's a good thing, because it means that BeginForeignModify()
and BeginDirectModify() are called at the same stage, from the FDW's
point of view. Even though BeginDirectModify() is called from
ForeignNext(), and BeginForeignModify() from ExecModifyTable(), that
difference isn't visible to the FDW; both are after executor startup but
before the first Iterate call.
- Heikki
Thanks for the review.
On Wed, Nov 11, 2020 at 5:55 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 10/11/2020 17:32, Heikki Linnakangas wrote:
On 10/11/2020 13:12, Amit Langote wrote:
On second thought, it seems A would amount to merely a cosmetic
adjustment of the API, nothing more. B seems to get the job done for
me and also doesn't unnecessarily break compatibility, so I've updated
0001 to implement B. Please give it a look.Looks good at a quick glance. It is a small API break that
BeginDirectModify() is now called during execution, not at executor
startup, but I don't think that's going to break FDWs in practice. One
could argue, though, that if we're going to change the API, we should do
it more loudly. So changing the arguments might be a good thing.The BeginDirectModify() and BeginForeignModify() interfaces are
inconsistent, but that's not this patch's fault. I wonder if we could
move the call to BeginForeignModify() also to ForeignNext(), though? And
BeginForeignScan() too, while we're at it.With these patches, BeginForeignModify() and BeginDirectModify() are
both called during execution, before the first
IterateForeignScan/IterateDirectModify call. The documentation for
BeginForeignModify() needs to be updated, it still claims that it's run
at executor startup, but that's not true after these patches. So that
needs to be updated.
Good point, I've updated the patch to note that.
I think that's a good thing, because it means that BeginForeignModify()
and BeginDirectModify() are called at the same stage, from the FDW's
point of view. Even though BeginDirectModify() is called from
ForeignNext(), and BeginForeignModify() from ExecModifyTable(), that
difference isn't visible to the FDW; both are after executor startup but
before the first Iterate call.
Right.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v9-0001-Set-ForeignScanState.resultRelInfo-lazily.patchapplication/octet-stream; name=v9-0001-Set-ForeignScanState.resultRelInfo-lazily.patchDownload
From f0204a89327c133b37d39fefbd4ba59fb5d9756a Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v9 1/2] Set ForeignScanState.resultRelInfo lazily
Instead of doing it in ExecInitForeignScan(), do it on the first
ForeignNext() call. This also moves the BeginDirectModify() call
into ForeignNext().
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily, that is as it begins executing,
instead of in ExecInitModifyTable().
---
doc/src/sgml/fdwhandler.sgml | 7 +++---
src/backend/executor/nodeForeignscan.c | 39 ++++++++++++++++++++++++----------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 9c92934..b1d7c84 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -890,9 +890,10 @@ BeginDirectModify(ForeignScanState *node,
</programlisting>
Prepare to execute a direct modification on the remote server.
- This is called during executor startup. It should perform any
- initialization needed prior to the direct modification (that should be
- done upon the first call to <function>IterateDirectModify</function>).
+ This is called right before the first time <function>IterateDirectModify</function>
+ is called on the node. It should perform any initialization needed prior to the
+ direct modification (that should be done upon the first call to
+ <function>IterateDirectModify</function>).
The <structname>ForeignScanState</structname> node has already been created, but
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0b20f94..5259abf 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -49,7 +49,31 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
+ {
+ /*
+ * For FDW's convenience, look up the result relation info and set
+ * ForeignScanState.resultRelInfo if not already done. This is also
+ * a good time to call BeginDirectModify().
+ */
+ Assert(plan->resultRelation > 0);
+ if (node->resultRelInfo == NULL)
+ {
+ EState *estate = node->ss.ps.state;
+ ResultRelInfo *rInfo = estate->es_result_relations[plan->resultRelation - 1];
+
+ /* ExecInitModifyTable() must have initialized one already. */
+ Assert(rInfo != NULL);
+ node->resultRelInfo = rInfo;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ Assert(rInfo->ri_FdwRoutine != NULL &&
+ rInfo->ri_FdwRoutine->BeginDirectModify != NULL);
+ rInfo->ri_FdwRoutine->BeginDirectModify(node,
+ estate->es_top_eflags);
+ MemoryContextSwitchTo(oldcontext);
+ }
slot = node->fdwroutine->IterateDirectModify(node);
+ }
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@@ -215,24 +239,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, any
+ * additional initializations are performed right before calling
+ * IterateDirectModify() for the first time.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
--
1.8.3.1
v9-0002-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v9-0002-Initialize-result-relation-information-lazily.patchDownload
From 9515f18f00c8a1d3e50de4c1af4b6b7c04ba6bc3 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v9 2/2] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also delays the initialization of
ModifyTableState.mt_partition_tuple_routing in the UPDATE case to the
first time ExecCrossPartitionUpdate() is called. That allows us to
get rid of the somewhat convoluted logic used to decide whether
ExecInitModifyTable() should initialize it. Related to that it the
lazy initialization of ri_ChildToRootMap in the ResultRelInfo of the
source partition of a tuple movement operation. Note that there is a
regression test output change in update.out resulting from this change
-- whereas previously a partition constraint violation error would be
reported as occurring on a leaf partition, it is now shown as occurring
on the query's target relation, which is valid because it is really
that table's (which is a sub-partitioned table) partition constraint
that is actually violated in the affected test cases.
While at it, also delay the opening of result relation indices,
ExecOpenIndices(), to the first time ExecInsert() or ExecUpdate() is
called.
---
doc/src/sgml/fdwhandler.sgml | 9 +-
src/backend/commands/explain.c | 5 +-
src/backend/executor/execMain.c | 6 +
src/backend/executor/execPartition.c | 116 +++--
src/backend/executor/nodeModifyTable.c | 877 ++++++++++++++++++---------------
src/include/executor/nodeModifyTable.h | 1 +
src/include/nodes/execnodes.h | 1 +
src/test/regress/expected/update.out | 12 +-
8 files changed, 567 insertions(+), 460 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index b1d7c84..d2ab497 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -520,10 +520,11 @@ BeginForeignModify(ModifyTableState *mtstate,
int eflags);
</programlisting>
- Begin executing a foreign table modification operation. This routine is
- called during executor startup. It should perform any initialization
- needed prior to the actual table modifications. Subsequently,
- <function>ExecForeignInsert</function>, <function>ExecForeignUpdate</function> or
+ Begin executing a foreign table modification operation. This is called
+ right before executing the subplan to fetch the tuples to be modified.
+ It should perform any initialization needed prior to the actual table
+ modifications. Subsequently, <function>ExecForeignInsert</function>,
+ <function>ExecForeignUpdate</function> or
<function>ExecForeignDelete</function> will be called for each tuple to be
inserted, updated, or deleted.
</para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43f9b01..95c613e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -19,6 +19,7 @@
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/nodeHash.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
#include "nodes/extensible.h"
@@ -3678,14 +3679,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ ExecGetResultRelation(mtstate, 0)->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..cdea3f8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1247,6 +1247,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1440,6 +1441,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..8265db2 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -508,6 +496,41 @@ ExecFindPartition(ModifyTableState *mtstate,
}
/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ {
+ result = ExecGetResultRelation(mtstate, elem->index);
+
+ /*
+ * This is required in order to convert the partition's tuple to be
+ * compatible with the root partitioned table's tuple descriptor. When
+ * generating the per-subplan result rels, this was not set.
+ */
+ result->ri_PartitionRoot = proute->partition_root;
+ }
+
+ return result;
+}
+
+/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
* partition Oid. We also populate the subplan ResultRelInfo with an
@@ -517,9 +540,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -530,26 +557,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -570,7 +597,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -606,19 +634,26 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo = ExecGetResultRelation(mtstate, 0);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -682,7 +717,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -741,7 +775,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -916,9 +949,14 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* from partition's rowtype to the root partition table's.
*/
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
+ {
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
+ /* First time creating the map for this result relation. */
+ Assert(!leaf_part_rri->ri_ChildToRootMapValid);
+ leaf_part_rri->ri_ChildToRootMapValid = true;
+ }
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7..0e4a76b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -180,6 +180,355 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
}
/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+static TupleConversionMap *
+GetChildToRootMap(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
+
+/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ /* Nope, so initialize. */
+ if (resultRelInfo == NULL)
+ {
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ PlanState *subplanstate = mtstate->mt_plans[whichrel];
+ Plan *subplan = subplanstate->plan;
+ bool junk_filter_needed = false;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+
+ /*
+ * If transition tuples will be captured, initialize a map to convert
+ * child tuples into the format of the table mentioned in the query
+ * (root relation), because the transition tuple store can only store
+ * tuples in the root table format. However for INSERT, the map is
+ * only initialized for a given partition when the partition itself is
+ * first initialized by ExecFindPartition. Also, this map is also
+ * needed if an UPDATE ends up having to move tuples across
+ * partitions, because in that case the child tuple to be moved first
+ * needs to be converted into the root table's format. In that case,
+ * we use GetChildToRootMap() to either create one from scratch if
+ * we didn't already create it here.
+ *
+ * Note: We cannot always initialize this map lazily, that is, use
+ * GetChildToRootMap(), because AfterTriggerSaveEvent(), which needs
+ * the map, doesn't have access to the "target" relation that is
+ * needed to create the map.
+ */
+ if (mtstate->mt_transition_capture && operation != CMD_INSERT)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ /* First time creating the map for this result relation. */
+ Assert(!resultRelInfo->ri_ChildToRootMapValid);
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize JunkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the
+ * tlist. UPDATE and DELETE always need a filter, since there's always
+ * at least one junk attribute present --- no need to look first.
+ * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+ * case of a foreign data wrapper it might be a set of junk attributes
+ * sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an
+ * INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * While at it, also initialize a result relation specific slot that
+ * will be used to copy the plan's output tuples into.
+ */
+ Assert(mtstate->mt_scans[whichrel] == NULL);
+ mtstate->mt_scans[whichrel] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
+/*
* ExecCheckTupleVisible -- verify tuple is visible
*
* It would not be consistent with guarantees of the higher isolation levels to
@@ -404,6 +753,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -1082,7 +1434,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;
@@ -1101,13 +1452,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, mtstate, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1161,7 +1526,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 = GetChildToRootMap(mtstate, resultRelInfo);
if (tupconv_map != NULL)
slot = execute_attr_map_slot(tupconv_map->attrMap,
slot,
@@ -1226,6 +1591,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1340,6 +1708,13 @@ lreplace:;
bool retry;
/*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
+ /*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
* root table, which will re-route it to the correct partition.
@@ -1929,11 +2304,12 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -1974,9 +2350,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -2000,17 +2374,26 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans) &&
+ resultRelInfo == NULL)
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2020,6 +2403,16 @@ ExecModifyTable(PlanState *pstate)
}
/*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
+ /*
* Ensure input tuple is the right format for the target relation.
*/
if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
@@ -2175,13 +2568,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2198,11 +2588,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2212,12 +2643,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* - the root partitioned table used for tuple routing.
*
* If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * elsewhere in the plan unless if it's an INSERT and its RT index is
+ * given explicitly in node->rootRelation. Otherwise (i.e. table
+ * inheritance) the target relation is the first relation in the
+ * node->resultRelations list.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
@@ -2225,13 +2657,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
else
{
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2244,268 +2680,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
ExecSetupTransitionCaptureState(mtstate, estate);
- /*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
/* Get the 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 an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
- /*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
* EvalPlanQual mechanism needs to be told about them. Locate the
@@ -2541,121 +2729,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2685,20 +2758,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 46a2dc9..9ae7e40 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,6 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel);
#endif /* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6c0a7d6..03a6555 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -497,6 +497,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..0ad0d1a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
1.8.3.1
I'm still a bit confused and unhappy about the initialization of
ResultRelInfos and the various fields in them. We've made progress in
the previous patches, but it's still a bit messy.
/*
* If transition tuples will be captured, initialize a map to convert
* child tuples into the format of the table mentioned in the query
* (root relation), because the transition tuple store can only store
* tuples in the root table format. However for INSERT, the map is
* only initialized for a given partition when the partition itself is
* first initialized by ExecFindPartition. Also, this map is also
* needed if an UPDATE ends up having to move tuples across
* partitions, because in that case the child tuple to be moved first
* needs to be converted into the root table's format. In that case,
* we use GetChildToRootMap() to either create one from scratch if
* we didn't already create it here.
*
* Note: We cannot always initialize this map lazily, that is, use
* GetChildToRootMap(), because AfterTriggerSaveEvent(), which needs
* the map, doesn't have access to the "target" relation that is
* needed to create the map.
*/
if (mtstate->mt_transition_capture && operation != CMD_INSERT)
{
Relation relation = resultRelInfo->ri_RelationDesc;
Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;resultRelInfo->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(relation),
RelationGetDescr(targetRel));
/* First time creating the map for this result relation. */
Assert(!resultRelInfo->ri_ChildToRootMapValid);
resultRelInfo->ri_ChildToRootMapValid = true;
}
The comment explains that AfterTriggerSaveEvent() cannot use
GetChildToRootMap(), because it doesn't have access to the root target
relation. But there is a field for that in ResultRelInfo:
ri_PartitionRoot. However, that's only set up when we do partition routing.
How about we rename ri_PartitionRoot to e.g ri_RootTarget, and set it
always, even for non-partition inheritance? We have that information
available when we initialize the ResultRelInfo, so might as well.
Some code currently checks ri_PartitionRoot, to determine if a tuple
that's been inserted, has been routed. For example:
/*
* Also check the tuple against the partition constraint, if there is
* one; except that if we got here via tuple-routing, we don't need to
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
(resultRelInfo->ri_PartitionRoot == NULL ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);
So if we set ri_PartitionRoot always, we would need some other way to
determine if the tuple at hand has actually been routed or not. But
wouldn't that be a good thing anyway? Isn't it possible that the same
ResultRelInfo is sometimes used for routed tuples, and sometimes for
tuples that have been inserted/updated "directly"?
ExecLookupUpdateResultRelByOid() sets that field lazily, so I think it
would be possible to get here with ri_PartitionRoot either set or not,
depending on whether an earlier cross-partition update was routed to the
table.
The above check is just an optimization, to skip unnecessary
ExecPartitionCheck() calls, but I think this snippet in
ExecConstraints() needs to get this right:
/*
* If the tuple has been routed, it's been converted to the
* partition's rowtype, which might differ from the root
* table's. We must convert it back to the root table's
* rowtype so that val_desc shown error message matches the
* input tuple.
*/
if (resultRelInfo->ri_PartitionRoot)
{
AttrMap *map;rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = build_attrmap_by_name_if_req(orig_tupdesc,
tupdesc);/*
* Partition-specific slot's tupdesc can't be changed, so
* allocate a new one.
*/
if (map != NULL)
slot = execute_attr_map_slot(map, slot,
MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
}
Is that an existing bug, or am I missing?
- Heikki
On Wed, Nov 11, 2020 at 10:14 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
I'm still a bit confused and unhappy about the initialization of
ResultRelInfos and the various fields in them. We've made progress in
the previous patches, but it's still a bit messy./*
* If transition tuples will be captured, initialize a map to convert
* child tuples into the format of the table mentioned in the query
* (root relation), because the transition tuple store can only store
* tuples in the root table format. However for INSERT, the map is
* only initialized for a given partition when the partition itself is
* first initialized by ExecFindPartition. Also, this map is also
* needed if an UPDATE ends up having to move tuples across
* partitions, because in that case the child tuple to be moved first
* needs to be converted into the root table's format. In that case,
* we use GetChildToRootMap() to either create one from scratch if
* we didn't already create it here.
*
* Note: We cannot always initialize this map lazily, that is, use
* GetChildToRootMap(), because AfterTriggerSaveEvent(), which needs
* the map, doesn't have access to the "target" relation that is
* needed to create the map.
*/
if (mtstate->mt_transition_capture && operation != CMD_INSERT)
{
Relation relation = resultRelInfo->ri_RelationDesc;
Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;resultRelInfo->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(relation),
RelationGetDescr(targetRel));
/* First time creating the map for this result relation. */
Assert(!resultRelInfo->ri_ChildToRootMapValid);
resultRelInfo->ri_ChildToRootMapValid = true;
}The comment explains that AfterTriggerSaveEvent() cannot use
GetChildToRootMap(), because it doesn't have access to the root target
relation. But there is a field for that in ResultRelInfo:
ri_PartitionRoot. However, that's only set up when we do partition routing.How about we rename ri_PartitionRoot to e.g ri_RootTarget, and set it
always, even for non-partition inheritance? We have that information
available when we initialize the ResultRelInfo, so might as well.
Yeah, I agree it's better to use ri_PartitionRoot more generally like
you describe here.
Some code currently checks ri_PartitionRoot, to determine if a tuple
that's been inserted, has been routed. For example:/*
* Also check the tuple against the partition constraint, if there is
* one; except that if we got here via tuple-routing, we don't need to
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
(resultRelInfo->ri_PartitionRoot == NULL ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);So if we set ri_PartitionRoot always, we would need some other way to
determine if the tuple at hand has actually been routed or not. But
wouldn't that be a good thing anyway? Isn't it possible that the same
ResultRelInfo is sometimes used for routed tuples, and sometimes for
tuples that have been inserted/updated "directly"?
ExecLookupUpdateResultRelByOid() sets that field lazily, so I think it
would be possible to get here with ri_PartitionRoot either set or not,
depending on whether an earlier cross-partition update was routed to the
table.
ri_RelationDesc != ri_PartitionRoot gives whether the result relation
is the original target relation of the query or not, so checking that
should be enough here.
The above check is just an optimization, to skip unnecessary
ExecPartitionCheck() calls, but I think this snippet in
ExecConstraints() needs to get this right:/*
* If the tuple has been routed, it's been converted to the
* partition's rowtype, which might differ from the root
* table's. We must convert it back to the root table's
* rowtype so that val_desc shown error message matches the
* input tuple.
*/
if (resultRelInfo->ri_PartitionRoot)
{
AttrMap *map;rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = build_attrmap_by_name_if_req(orig_tupdesc,
tupdesc);/*
* Partition-specific slot's tupdesc can't be changed, so
* allocate a new one.
*/
if (map != NULL)
slot = execute_attr_map_slot(map, slot,
MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
}Is that an existing bug, or am I missing?
What it's doing is converting a routed tuple in the partition's tuple
format back into the original target relation's format before showing
the tuple in the error message. Note that we do this reverse
conversion only for tuple routing target relations, not all child
result relations, so in that sense it's a bit inconsistent. Maybe we
don't need to be too pedantic about showing the exact same tuple as
the user inserted (that is, one matching the "root" table's column
order), so it seems okay to just remove these reverse-conversion
blocks that are repeated in a number of places that show an error
message after failing a constraint check.
Attached new 0002 which does these adjustments. I went with
ri_RootTargetDesc to go along with ri_RelationDesc.
Also, I have updated the original 0002 (now 0003) to make
GetChildToRootMap() use ri_RootTargetDesc instead of
ModifyTableState.rootResultRelInfo.ri_RelationDesc, so that even
AfterTriggerSaveEvent() can now use that function. This allows us to
avoid having to initialize ri_ChildToRootMap anywhere but inside
GetChildRootMap(), with that long comment defending doing so. :-)
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v10-0002-Rethink-ResultRelInfo.ri_PartitionRoot.patchapplication/octet-stream; name=v10-0002-Rethink-ResultRelInfo.ri_PartitionRoot.patchDownload
From 28d9e6409b6f6ec2a38adbc9dbaa44aed11f01c4 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 12 Nov 2020 15:04:02 +0900
Subject: [PATCH v10 2/3] Rethink ResultRelInfo.ri_PartitionRoot
Its current usage is specific to partition tuple routing, although
there are other places that could use it do determine the original
target relation of the query without having access to the
ModifyTableState. So set it in all result relations, not just
those that are targets of tuple routing. While at it, also rename
the field to ri_RootTargetDesc to denote its wider scope.
This also removes the many instances of a stanza to convert a routed
tuple in the partition's format into the format of the original target
relation of the query in favor of just showing the partition format
tuple in the error messages, which seems harmless, but wasn't thought
to be so when the code was originally written.
---
src/backend/commands/copy.c | 2 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/executor/execMain.c | 181 ++++----------------------
src/backend/executor/execPartition.c | 11 +-
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 18 +--
src/backend/replication/logical/worker.c | 6 +-
src/include/executor/executor.h | 4 +-
src/include/nodes/execnodes.h | 4 +-
src/test/regress/expected/inherit.out | 8 +-
src/test/regress/expected/insert.out | 6 +-
src/test/regress/expected/updatable_views.out | 4 +-
src/test/regress/expected/update.out | 2 +-
13 files changed, 61 insertions(+), 191 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 115860a..25a2f1c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2830,7 +2830,7 @@ CopyFrom(CopyState cstate)
*/
ExecInitRangeTable(estate, cstate->range_table);
resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
- ExecInitResultRelation(estate, resultRelInfo, 1);
+ ExecInitResultRelation(estate, resultRelInfo, 1, cstate->rel);
/* Verify the named relation is a valid target for INSERT */
CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e3cfaf8..78eddec 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1805,7 +1805,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
- NULL,
+ rel,
0);
estate->es_opened_result_relations =
lappend(estate->es_opened_result_relations, resultRelInfo);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..d133a14 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -93,11 +93,10 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
Bitmapset *modifiedCols,
AclMode requiredPerms);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
- TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
- int maxfieldlen);
+static char *ExecBuildSlotValueDescription(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot,
+ int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
/*
@@ -1196,13 +1195,14 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- Relation partition_root,
+ Relation rootTargetDesc,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
resultRelInfo->type = T_ResultRelInfo;
resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
resultRelInfo->ri_RelationDesc = resultRelationDesc;
+ resultRelInfo->ri_RootTargetDesc = rootTargetDesc;
resultRelInfo->ri_NumIndices = 0;
resultRelInfo->ri_IndexRelationDescs = NULL;
resultRelInfo->ri_IndexRelationInfo = NULL;
@@ -1242,7 +1242,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ReturningSlot = NULL;
resultRelInfo->ri_TrigOldSlot = NULL;
resultRelInfo->ri_TrigNewSlot = NULL;
- resultRelInfo->ri_PartitionRoot = partition_root;
resultRelInfo->ri_RootToPartitionMap = NULL; /* set by
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
@@ -1321,7 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
- NULL,
+ rel,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -1733,50 +1732,11 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
EState *estate)
{
- Oid root_relid;
- TupleDesc tupdesc;
char *val_desc;
- Bitmapset *modifiedCols;
-
- /*
- * If the tuple has been routed, it's been converted to the partition's
- * rowtype, which might differ from the root table's. We must convert it
- * back to the root table's rowtype so that val_desc in the error message
- * matches the input tuple.
- */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc;
- AttrMap *map;
-
- root_relid = RelationGetRelid(resultRelInfo->ri_PartitionRoot);
- tupdesc = RelationGetDescr(resultRelInfo->ri_PartitionRoot);
-
- old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so allocate a
- * new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
- else
- {
- root_relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
- tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- }
-
- modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
- GetUpdatedColumns(resultRelInfo, estate));
- val_desc = ExecBuildSlotValueDescription(root_relid,
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
@@ -1804,9 +1764,6 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
TupleConstr *constr = tupdesc->constr;
- Bitmapset *modifiedCols;
- Bitmapset *insertedCols;
- Bitmapset *updatedCols;
Assert(constr); /* we should not be called otherwise */
@@ -1821,52 +1778,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
if (att->attnotnull && slot_attisnull(slot, attrChk))
{
- char *val_desc;
- Relation orig_rel = rel;
- TupleDesc orig_tupdesc = RelationGetDescr(rel);
-
- /*
- * If the tuple has been routed, it's been converted to the
- * partition's rowtype, which might differ from the root
- * table's. We must convert it back to the root table's
- * rowtype so that val_desc shown error message matches the
- * input tuple.
- */
- if (resultRelInfo->ri_PartitionRoot)
- {
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(orig_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so
- * allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
+ char *val_desc;
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" of relation \"%s\" violates not-null constraint",
NameStr(att->attname),
- RelationGetRelationName(orig_rel)),
+ RelationGetRelationName(rel)),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
- errtablecol(orig_rel, attrChk)));
+ errtablecol(rel, attrChk)));
}
}
}
@@ -1878,43 +1803,16 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
{
char *val_desc;
- Relation orig_rel = rel;
- /* See the comment above. */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc = RelationGetDescr(rel);
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so
- * allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate, resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
- RelationGetRelationName(orig_rel), failed),
+ RelationGetRelationName(rel), failed),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
- errtableconstraint(orig_rel, failed)));
+ errtableconstraint(rel, failed)));
}
}
}
@@ -1932,8 +1830,6 @@ void
ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
{
- Relation rel = resultRelInfo->ri_RelationDesc;
- TupleDesc tupdesc = RelationGetDescr(rel);
ExprContext *econtext;
ListCell *l1,
*l2;
@@ -1971,9 +1867,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
if (!ExecQual(wcoExpr, econtext))
{
char *val_desc;
- Bitmapset *modifiedCols;
- Bitmapset *insertedCols;
- Bitmapset *updatedCols;
switch (wco->kind)
{
@@ -1987,34 +1880,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* USING policy.
*/
case WCO_VIEW_CHECK:
- /* See the comment in ExecConstraints(). */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc = RelationGetDescr(rel);
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed,
- * so allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
@@ -2077,10 +1945,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* columns they are.
*/
static char *
-ExecBuildSlotValueDescription(Oid reloid,
+ExecBuildSlotValueDescription(EState *estate,
+ ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
int maxfieldlen)
{
StringInfoData buf;
@@ -2091,6 +1958,9 @@ ExecBuildSlotValueDescription(Oid reloid,
AclResult aclresult;
bool table_perm = false;
bool any_perm = false;
+ Oid reloid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ TupleDesc tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+ Bitmapset *modifiedCols;
/*
* Check if RLS is enabled and should be active for the relation; if so,
@@ -2100,6 +1970,9 @@ ExecBuildSlotValueDescription(Oid reloid,
if (check_enable_rls(reloid, InvalidOid, true) == RLS_ENABLED)
return NULL;
+ modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
+ GetUpdatedColumns(resultRelInfo, estate));
+
initStringInfo(&buf);
appendStringInfoChar(&buf, '(');
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..f540249 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -542,13 +542,6 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
}
}
@@ -918,7 +911,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
+ RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
@@ -962,7 +955,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
* partition from the parent's type to the partition's.
*/
partRelInfo->ri_RootToPartitionMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RootTargetDesc),
RelationGetDescr(partRelInfo->ri_RelationDesc));
/*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 071a000..9819e7a 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -831,7 +831,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
*/
void
ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
- Index rti)
+ Index rti, Relation rootTargetDesc)
{
Relation resultRelationDesc;
@@ -839,7 +839,7 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
InitResultRelInfo(resultRelInfo,
resultRelationDesc,
rti,
- NULL,
+ rootTargetDesc,
estate->es_instrument);
if (estate->es_result_relations == NULL)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7..24d44f5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -508,7 +508,8 @@ ExecInsert(ModifyTableState *mtstate,
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
- (resultRelInfo->ri_PartitionRoot == NULL ||
+ (resultRelInfo->ri_RelationDesc ==
+ resultRelInfo->ri_RootTargetDesc ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);
@@ -2219,15 +2220,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
if (node->rootRelation > 0)
{
+ rel = ExecGetRangeTableRelation(estate, node->rootRelation);
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
- node->rootRelation);
+ node->rootRelation, rel);
}
else
{
+ Index rootRelation = linitial_int(node->resultRelations);
+
+ rel = ExecGetRangeTableRelation(estate, rootRelation);
mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
+ rel);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
@@ -2263,7 +2268,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* was initialized already.)
*/
if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+ ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
/* Initialize the usesFdwDirectModify flag */
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
@@ -2340,9 +2345,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
i++;
}
- /* Get the target relation */
- rel = mtstate->rootResultRelInfo->ri_RelationDesc;
-
/*
* If it's not a partitioned table after all, UPDATE tuple routing should
* not be attempted.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 0468491..a46af55 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1171,7 +1171,7 @@ apply_handle_insert(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
/* Input functions may need an active snapshot, so get one */
PushActiveSnapshot(GetTransactionSnapshot());
@@ -1296,7 +1296,7 @@ apply_handle_update(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
/*
* Populate updatedCols so that per-column triggers can fire. This could
@@ -1452,7 +1452,7 @@ apply_handle_delete(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
PushActiveSnapshot(GetTransactionSnapshot());
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 0c48d2a..2bc349b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -191,7 +191,7 @@ extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- Relation partition_root,
+ Relation rootTargetDesc,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
@@ -551,7 +551,7 @@ exec_rt_fetch(Index rti, EState *estate)
extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
- Index rti);
+ Index rti, Relation rootTargetDesc);
extern int executor_errposition(EState *estate, int location);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6c0a7d6..5bee505 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -411,6 +411,9 @@ typedef struct ResultRelInfo
/* relation descriptor for result relation */
Relation ri_RelationDesc;
+ /* relation descriptor of the original target relation */
+ Relation ri_RootTargetDesc;
+
/* # of indices existing on result relation */
int ri_NumIndices;
@@ -487,7 +490,6 @@ typedef struct ResultRelInfo
* ExecInitRoutingInfo, are non-NULL if partition has a different tuple
* format than the root table.
*/
- Relation ri_PartitionRoot;
TupleConversionMap *ri_RootToPartitionMap;
TupleTableSlot *ri_PartitionTupleSlot;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef..42efca0 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2458,7 +2458,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 10).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10');
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (20, 1, 10).
+DETAIL: Failing row contains (10, 1, 20).
-- insert with child not null constraint error
INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL);
ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint
@@ -2468,7 +2468,7 @@ ERROR: null value in column "data" of relation "errtst_child_plaindef" violates
DETAIL: Failing row contains (10, 1, null).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL);
ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint
-DETAIL: Failing row contains (20, 1, null).
+DETAIL: Failing row contains (null, 1, 20).
-- insert with shared check constraint error
INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5');
ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small"
@@ -2478,7 +2478,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 5, 5).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5');
ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small"
-DETAIL: Failing row contains (20, 5, 5).
+DETAIL: Failing row contains (5, 5, 20).
-- within partition update without child check constraint violation
BEGIN;
UPDATE errtst_parent SET data = data + 1 WHERE partid = 0;
@@ -2523,7 +2523,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 15).
UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10;
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (20, 1, 15).
+DETAIL: Failing row contains (15, 1, 20).
UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20;
ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check"
DETAIL: Failing row contains (0, 1, 15).
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index da50ee3..f132d74 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -546,7 +546,7 @@ create trigger mlparted11_trig before insert ON mlparted11
-- to the BR trigger mlparted11_trig_fn)
insert into mlparted values (1, 2);
ERROR: new row for relation "mlparted11" violates check constraint "check_b"
-DETAIL: Failing row contains (1, 4).
+DETAIL: Failing row contains (4, 1).
drop trigger mlparted11_trig on mlparted11;
drop function mlparted11_trig_fn();
-- check that inserting into an internal partition successfully results in
@@ -592,12 +592,12 @@ alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 5
alter table mlparted add constraint check_b check (a = 1 and b < 45);
insert into mlparted values (1, 45, 'a');
ERROR: new row for relation "mlparted5a" violates check constraint "check_b"
-DETAIL: Failing row contains (1, 45, a).
+DETAIL: Failing row contains (1, a, 45).
create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b'; return new; end; $$ language plpgsql;
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
-DETAIL: Failing row contains (b, 1, 40).
+DETAIL: Failing row contains (1, b, 40).
drop table mlparted5;
alter table mlparted drop constraint check_b;
-- Check multi-level default partition
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 6a97700..a8807df 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2648,7 +2648,7 @@ select tableoid::regclass, * from uv_pt;
create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
insert into uv_ptv_wco values (1, 2);
ERROR: new row violates check option for view "uv_ptv_wco"
-DETAIL: Failing row contains (1, 2, null).
+DETAIL: Failing row contains (2, null, 1).
drop view uv_ptv, uv_ptv_wco;
drop table uv_pt, uv_pt1, uv_pt11;
-- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
@@ -2674,7 +2674,7 @@ with check option;
-- rowtype after tuple-routing
insert into wcowrtest_v2 values (2, 'no such row in sometable');
ERROR: new row violates check option for view "wcowrtest_v2"
-DETAIL: Failing row contains (2, no such row in sometable).
+DETAIL: Failing row contains (no such row in sometable, 2).
drop view wcowrtest_v, wcowrtest_v2;
drop table wcowrtest, sometable;
-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..8819921 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -409,7 +409,7 @@ DETAIL: Failing row contains (a, 4, 120, 1, 1).
-- fail, row movement with check option violation
UPDATE upview set a = 'b', b = 15, c = 120 WHERE b = 4;
ERROR: new row violates check option for view "upview"
-DETAIL: Failing row contains (b, 15, 120, 1, 1).
+DETAIL: Failing row contains (1, 120, 1, b, 15).
-- ok, row movement, check option passes
UPDATE upview set a = 'b', b = 15 WHERE b = 4;
:show_data;
--
1.8.3.1
v10-0001-Set-ForeignScanState.resultRelInfo-lazily.patchapplication/octet-stream; name=v10-0001-Set-ForeignScanState.resultRelInfo-lazily.patchDownload
From 84c124caf06216eef944d57517311f389968b01d Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v10 1/3] Set ForeignScanState.resultRelInfo lazily
Instead of doing it in ExecInitForeignScan(), do it on the first
ForeignNext() call. This also moves the BeginDirectModify() call
into ForeignNext().
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily, that is as it begins executing,
instead of in ExecInitModifyTable().
---
doc/src/sgml/fdwhandler.sgml | 7 +++---
src/backend/executor/nodeForeignscan.c | 39 ++++++++++++++++++++++++----------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 9c92934..b1d7c84 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -890,9 +890,10 @@ BeginDirectModify(ForeignScanState *node,
</programlisting>
Prepare to execute a direct modification on the remote server.
- This is called during executor startup. It should perform any
- initialization needed prior to the direct modification (that should be
- done upon the first call to <function>IterateDirectModify</function>).
+ This is called right before the first time <function>IterateDirectModify</function>
+ is called on the node. It should perform any initialization needed prior to the
+ direct modification (that should be done upon the first call to
+ <function>IterateDirectModify</function>).
The <structname>ForeignScanState</structname> node has already been created, but
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0b20f94..5259abf 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -49,7 +49,31 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
+ {
+ /*
+ * For FDW's convenience, look up the result relation info and set
+ * ForeignScanState.resultRelInfo if not already done. This is also
+ * a good time to call BeginDirectModify().
+ */
+ Assert(plan->resultRelation > 0);
+ if (node->resultRelInfo == NULL)
+ {
+ EState *estate = node->ss.ps.state;
+ ResultRelInfo *rInfo = estate->es_result_relations[plan->resultRelation - 1];
+
+ /* ExecInitModifyTable() must have initialized one already. */
+ Assert(rInfo != NULL);
+ node->resultRelInfo = rInfo;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ Assert(rInfo->ri_FdwRoutine != NULL &&
+ rInfo->ri_FdwRoutine->BeginDirectModify != NULL);
+ rInfo->ri_FdwRoutine->BeginDirectModify(node,
+ estate->es_top_eflags);
+ MemoryContextSwitchTo(oldcontext);
+ }
slot = node->fdwroutine->IterateDirectModify(node);
+ }
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@@ -215,24 +239,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, any
+ * additional initializations are performed right before calling
+ * IterateDirectModify() for the first time.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
--
1.8.3.1
v10-0003-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v10-0003-Initialize-result-relation-information-lazily.patchDownload
From ca37c03addea47924b80bd406251573494371b94 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v10 3/3] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also delays the initialization of
ModifyTableState.mt_partition_tuple_routing in the UPDATE case to the
first time ExecCrossPartitionUpdate() is called. That allows us to
get rid of the somewhat convoluted logic used to decide whether
ExecInitModifyTable() should initialize it. Related to that it the
lazy initialization of ri_ChildToRootMap in the ResultRelInfo of the
source partition of a tuple movement operation. Note that there is a
regression test output change in update.out resulting from this change
-- whereas previously a partition constraint violation error would be
reported as occurring on a leaf partition, it is now shown as occurring
on the query's target relation, which is valid because it is really
that table's (which is a sub-partitioned table) partition constraint
that is actually violated in the affected test cases.
While at it, also delay the opening of result relation indices,
ExecOpenIndices(), to the first time ExecInsert() or ExecUpdate() is
called.
---
doc/src/sgml/fdwhandler.sgml | 9 +-
src/backend/commands/explain.c | 12 +-
src/backend/commands/trigger.c | 2 +-
src/backend/executor/execMain.c | 6 +
src/backend/executor/execPartition.c | 106 +++--
src/backend/executor/execUtils.c | 21 +
src/backend/executor/nodeModifyTable.c | 829 +++++++++++++++++----------------
src/include/executor/executor.h | 5 +
src/include/nodes/execnodes.h | 1 +
src/test/regress/expected/update.out | 12 +-
10 files changed, 540 insertions(+), 463 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index b1d7c84..d2ab497 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -520,10 +520,11 @@ BeginForeignModify(ModifyTableState *mtstate,
int eflags);
</programlisting>
- Begin executing a foreign table modification operation. This routine is
- called during executor startup. It should perform any initialization
- needed prior to the actual table modifications. Subsequently,
- <function>ExecForeignInsert</function>, <function>ExecForeignUpdate</function> or
+ Begin executing a foreign table modification operation. This is called
+ right before executing the subplan to fetch the tuples to be modified.
+ It should perform any initialization needed prior to the actual table
+ modifications. Subsequently, <function>ExecForeignInsert</function>,
+ <function>ExecForeignUpdate</function> or
<function>ExecForeignDelete</function> will be called for each tuple to be
inserted, updated, or deleted.
</para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43f9b01..ec79557 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -3654,6 +3655,8 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
int j;
List *idxNames = NIL;
ListCell *lst;
+ ResultRelInfo *firstResultRel;
+ Relation rootTargetDesc;
switch (node->operation)
{
@@ -3675,17 +3678,22 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
break;
}
+ Assert(mtstate->rootResultRelInfo != NULL);
+ rootTargetDesc = mtstate->rootResultRelInfo->ri_RelationDesc;
+ firstResultRel = ExecGetResultRelation(mtstate, 0, rootTargetDesc);
+
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ firstResultRel->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j,
+ rootTargetDesc);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e1f3472..c4002fb 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -5385,7 +5385,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = relinfo->ri_ChildToRootMap;
+ TupleConversionMap *map = ExecGetChildToRootMap(relinfo);
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d133a14..5828c01 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1246,6 +1246,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1439,6 +1440,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index f540249..2973ac5 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -508,6 +496,33 @@ ExecFindPartition(ModifyTableState *mtstate,
}
/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ result = ExecGetResultRelation(mtstate, elem->index,
+ proute->partition_root);
+
+ return result;
+}
+
+/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
* partition Oid. We also populate the subplan ResultRelInfo with an
@@ -517,9 +532,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -530,19 +549,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -563,7 +589,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -599,19 +626,27 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo =
+ ExecGetResultRelation(mtstate, 0, proute->partition_root);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -675,7 +710,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -734,7 +768,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -905,15 +938,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
/*
- * Also, if transition capture is required, store a map to convert tuples
- * from partition's rowtype to the root partition table's.
- */
- if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
- leaf_part_rri->ri_ChildToRootMap =
- convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
-
- /*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9819e7a..769362f 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1223,3 +1223,24 @@ ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo)
return relInfo->ri_ReturningSlot;
}
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+TupleConversionMap *
+ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = resultRelInfo->ri_RootTargetDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 24d44f5..850e815 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -180,6 +180,304 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
}
/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+ Relation rootTargetDesc)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ /* Nope, so initialize. */
+ if (resultRelInfo == NULL)
+ {
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ PlanState *subplanstate = mtstate->mt_plans[whichrel];
+ Plan *subplan = subplanstate->plan;
+ bool junk_filter_needed = false;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti, rootTargetDesc);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize JunkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the
+ * tlist. UPDATE and DELETE always need a filter, since there's always
+ * at least one junk attribute present --- no need to look first.
+ * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+ * case of a foreign data wrapper it might be a set of junk attributes
+ * sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an
+ * INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * While at it, also initialize a result relation specific slot that
+ * will be used to copy the plan's output tuples into.
+ */
+ Assert(mtstate->mt_scans[whichrel] == NULL);
+ mtstate->mt_scans[whichrel] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
+/*
* ExecCheckTupleVisible -- verify tuple is visible
*
* It would not be consistent with guarantees of the higher isolation levels to
@@ -404,6 +702,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -1083,7 +1384,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;
@@ -1102,13 +1402,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, mtstate, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1162,7 +1476,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,
@@ -1227,6 +1541,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1341,6 +1658,13 @@ lreplace:;
bool retry;
/*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
+ /*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
* root table, which will re-route it to the correct partition.
@@ -1930,17 +2254,19 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
ItemPointerData tuple_ctid;
HeapTupleData oldtupdata;
HeapTuple oldtuple;
+ Relation rootTargetDesc = node->rootResultRelInfo->ri_RelationDesc;
CHECK_FOR_INTERRUPTS();
@@ -1975,9 +2301,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -2001,17 +2325,27 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL &&
+ bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans))
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+ rootTargetDesc);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2021,6 +2355,17 @@ ExecModifyTable(PlanState *pstate)
}
/*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+ rootTargetDesc);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
+ /*
* Ensure input tuple is the right format for the target relation.
*/
if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
@@ -2176,13 +2521,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2199,11 +2541,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2213,12 +2596,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* - the root partitioned table used for tuple routing.
*
* If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * elsewhere in the plan unless if it's an INSERT and its RT index is
+ * given explicitly in node->rootRelation. Otherwise (i.e. table
+ * inheritance) the target relation is the first relation in the
+ * node->resultRelations list.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
rel = ExecGetRangeTableRelation(estate, node->rootRelation);
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
@@ -2229,14 +2613,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
Index rootRelation = linitial_int(node->resultRelations);
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
rel = ExecGetRangeTableRelation(estate, rootRelation);
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
- rel);
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0, rel);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2250,264 +2638,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
- /*
- * 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 an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
- /*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
* EvalPlanQual mechanism needs to be told about them. Locate the
@@ -2543,121 +2683,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2687,20 +2712,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 2bc349b..4b79267 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -573,6 +573,7 @@ extern int ExecCleanTargetListLength(List *targetlist);
extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
+extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
/*
* prototypes from functions in execIndexing.c
@@ -616,4 +617,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+ Relation rootTargetDesc);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5bee505..8ffa2c1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -499,6 +499,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 8819921..51f42ab 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
1.8.3.1
On Thu, Nov 12, 2020 at 5:04 PM Amit Langote <amitlangote09@gmail.com> wrote:
Attached new 0002 which does these adjustments. I went with
ri_RootTargetDesc to go along with ri_RelationDesc.Also, I have updated the original 0002 (now 0003) to make
GetChildToRootMap() use ri_RootTargetDesc instead of
ModifyTableState.rootResultRelInfo.ri_RelationDesc, so that even
AfterTriggerSaveEvent() can now use that function. This allows us to
avoid having to initialize ri_ChildToRootMap anywhere but inside
GetChildRootMap(), with that long comment defending doing so. :-)
These needed to be rebased due to recent copy.c upheavals. Attached.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v11-0001-Set-ForeignScanState.resultRelInfo-lazily.patchapplication/octet-stream; name=v11-0001-Set-ForeignScanState.resultRelInfo-lazily.patchDownload
From af6856dd28b9705e402a198779491e1b2ef9a743 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v11 1/3] Set ForeignScanState.resultRelInfo lazily
Instead of doing it in ExecInitForeignScan(), do it on the first
ForeignNext() call. This also moves the BeginDirectModify() call
into ForeignNext().
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily, that is as it begins executing,
instead of in ExecInitModifyTable().
---
doc/src/sgml/fdwhandler.sgml | 7 +++---
src/backend/executor/nodeForeignscan.c | 39 ++++++++++++++++++++++++----------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 9c92934..b1d7c84 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -890,9 +890,10 @@ BeginDirectModify(ForeignScanState *node,
</programlisting>
Prepare to execute a direct modification on the remote server.
- This is called during executor startup. It should perform any
- initialization needed prior to the direct modification (that should be
- done upon the first call to <function>IterateDirectModify</function>).
+ This is called right before the first time <function>IterateDirectModify</function>
+ is called on the node. It should perform any initialization needed prior to the
+ direct modification (that should be done upon the first call to
+ <function>IterateDirectModify</function>).
The <structname>ForeignScanState</structname> node has already been created, but
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0b20f94..5259abf 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -49,7 +49,31 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
+ {
+ /*
+ * For FDW's convenience, look up the result relation info and set
+ * ForeignScanState.resultRelInfo if not already done. This is also
+ * a good time to call BeginDirectModify().
+ */
+ Assert(plan->resultRelation > 0);
+ if (node->resultRelInfo == NULL)
+ {
+ EState *estate = node->ss.ps.state;
+ ResultRelInfo *rInfo = estate->es_result_relations[plan->resultRelation - 1];
+
+ /* ExecInitModifyTable() must have initialized one already. */
+ Assert(rInfo != NULL);
+ node->resultRelInfo = rInfo;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ Assert(rInfo->ri_FdwRoutine != NULL &&
+ rInfo->ri_FdwRoutine->BeginDirectModify != NULL);
+ rInfo->ri_FdwRoutine->BeginDirectModify(node,
+ estate->es_top_eflags);
+ MemoryContextSwitchTo(oldcontext);
+ }
slot = node->fdwroutine->IterateDirectModify(node);
+ }
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@@ -215,24 +239,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, any
+ * additional initializations are performed right before calling
+ * IterateDirectModify() for the first time.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
--
1.8.3.1
v11-0002-Rethink-ResultRelInfo.ri_PartitionRoot.patchapplication/octet-stream; name=v11-0002-Rethink-ResultRelInfo.ri_PartitionRoot.patchDownload
From 6aaeb002744be6544438c7c33bf01591f4038dd0 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 12 Nov 2020 15:04:02 +0900
Subject: [PATCH v11 2/3] Rethink ResultRelInfo.ri_PartitionRoot
Its current usage is specific to partition tuple routing, although
there are other places that could use it do determine the original
target relation of the query without having access to the
ModifyTableState. So set it in all result relations, not just
those that are targets of tuple routing. While at it, also rename
the field to ri_RootTargetDesc to denote its wider scope.
This also removes the many instances of a stanza to convert a routed
tuple in the partition's format into the format of the original target
relation of the query in favor of just showing the partition format
tuple in the error messages, which seems harmless, but wasn't thought
to be so when the code was originally written.
---
src/backend/commands/copyfrom.c | 2 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/executor/execMain.c | 181 ++++----------------------
src/backend/executor/execPartition.c | 11 +-
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 18 +--
src/backend/replication/logical/worker.c | 6 +-
src/include/executor/executor.h | 4 +-
src/include/nodes/execnodes.h | 4 +-
src/test/regress/expected/inherit.out | 8 +-
src/test/regress/expected/insert.out | 6 +-
src/test/regress/expected/updatable_views.out | 4 +-
src/test/regress/expected/update.out | 2 +-
13 files changed, 61 insertions(+), 191 deletions(-)
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a..804526e 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -648,7 +648,7 @@ CopyFrom(CopyFromState cstate)
*/
ExecInitRangeTable(estate, cstate->range_table);
resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
- ExecInitResultRelation(estate, resultRelInfo, 1);
+ ExecInitResultRelation(estate, resultRelInfo, 1, cstate->rel);
/* Verify the named relation is a valid target for INSERT */
CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 46f1637..0d57bd1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1805,7 +1805,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
- NULL,
+ rel,
0);
estate->es_opened_result_relations =
lappend(estate->es_opened_result_relations, resultRelInfo);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..d133a14 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -93,11 +93,10 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
Bitmapset *modifiedCols,
AclMode requiredPerms);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
- TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
- int maxfieldlen);
+static char *ExecBuildSlotValueDescription(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot,
+ int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
/*
@@ -1196,13 +1195,14 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- Relation partition_root,
+ Relation rootTargetDesc,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
resultRelInfo->type = T_ResultRelInfo;
resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
resultRelInfo->ri_RelationDesc = resultRelationDesc;
+ resultRelInfo->ri_RootTargetDesc = rootTargetDesc;
resultRelInfo->ri_NumIndices = 0;
resultRelInfo->ri_IndexRelationDescs = NULL;
resultRelInfo->ri_IndexRelationInfo = NULL;
@@ -1242,7 +1242,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ReturningSlot = NULL;
resultRelInfo->ri_TrigOldSlot = NULL;
resultRelInfo->ri_TrigNewSlot = NULL;
- resultRelInfo->ri_PartitionRoot = partition_root;
resultRelInfo->ri_RootToPartitionMap = NULL; /* set by
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
@@ -1321,7 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
- NULL,
+ rel,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -1733,50 +1732,11 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
EState *estate)
{
- Oid root_relid;
- TupleDesc tupdesc;
char *val_desc;
- Bitmapset *modifiedCols;
-
- /*
- * If the tuple has been routed, it's been converted to the partition's
- * rowtype, which might differ from the root table's. We must convert it
- * back to the root table's rowtype so that val_desc in the error message
- * matches the input tuple.
- */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc;
- AttrMap *map;
-
- root_relid = RelationGetRelid(resultRelInfo->ri_PartitionRoot);
- tupdesc = RelationGetDescr(resultRelInfo->ri_PartitionRoot);
-
- old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so allocate a
- * new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
- else
- {
- root_relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
- tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- }
-
- modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
- GetUpdatedColumns(resultRelInfo, estate));
- val_desc = ExecBuildSlotValueDescription(root_relid,
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
@@ -1804,9 +1764,6 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
TupleConstr *constr = tupdesc->constr;
- Bitmapset *modifiedCols;
- Bitmapset *insertedCols;
- Bitmapset *updatedCols;
Assert(constr); /* we should not be called otherwise */
@@ -1821,52 +1778,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
if (att->attnotnull && slot_attisnull(slot, attrChk))
{
- char *val_desc;
- Relation orig_rel = rel;
- TupleDesc orig_tupdesc = RelationGetDescr(rel);
-
- /*
- * If the tuple has been routed, it's been converted to the
- * partition's rowtype, which might differ from the root
- * table's. We must convert it back to the root table's
- * rowtype so that val_desc shown error message matches the
- * input tuple.
- */
- if (resultRelInfo->ri_PartitionRoot)
- {
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(orig_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so
- * allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
+ char *val_desc;
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" of relation \"%s\" violates not-null constraint",
NameStr(att->attname),
- RelationGetRelationName(orig_rel)),
+ RelationGetRelationName(rel)),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
- errtablecol(orig_rel, attrChk)));
+ errtablecol(rel, attrChk)));
}
}
}
@@ -1878,43 +1803,16 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
{
char *val_desc;
- Relation orig_rel = rel;
- /* See the comment above. */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc = RelationGetDescr(rel);
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so
- * allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate, resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
- RelationGetRelationName(orig_rel), failed),
+ RelationGetRelationName(rel), failed),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
- errtableconstraint(orig_rel, failed)));
+ errtableconstraint(rel, failed)));
}
}
}
@@ -1932,8 +1830,6 @@ void
ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
{
- Relation rel = resultRelInfo->ri_RelationDesc;
- TupleDesc tupdesc = RelationGetDescr(rel);
ExprContext *econtext;
ListCell *l1,
*l2;
@@ -1971,9 +1867,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
if (!ExecQual(wcoExpr, econtext))
{
char *val_desc;
- Bitmapset *modifiedCols;
- Bitmapset *insertedCols;
- Bitmapset *updatedCols;
switch (wco->kind)
{
@@ -1987,34 +1880,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* USING policy.
*/
case WCO_VIEW_CHECK:
- /* See the comment in ExecConstraints(). */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc = RelationGetDescr(rel);
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed,
- * so allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
@@ -2077,10 +1945,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* columns they are.
*/
static char *
-ExecBuildSlotValueDescription(Oid reloid,
+ExecBuildSlotValueDescription(EState *estate,
+ ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
int maxfieldlen)
{
StringInfoData buf;
@@ -2091,6 +1958,9 @@ ExecBuildSlotValueDescription(Oid reloid,
AclResult aclresult;
bool table_perm = false;
bool any_perm = false;
+ Oid reloid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ TupleDesc tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+ Bitmapset *modifiedCols;
/*
* Check if RLS is enabled and should be active for the relation; if so,
@@ -2100,6 +1970,9 @@ ExecBuildSlotValueDescription(Oid reloid,
if (check_enable_rls(reloid, InvalidOid, true) == RLS_ENABLED)
return NULL;
+ modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
+ GetUpdatedColumns(resultRelInfo, estate));
+
initStringInfo(&buf);
appendStringInfoChar(&buf, '(');
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..f540249 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -542,13 +542,6 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
}
}
@@ -918,7 +911,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
+ RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
@@ -962,7 +955,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
* partition from the parent's type to the partition's.
*/
partRelInfo->ri_RootToPartitionMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RootTargetDesc),
RelationGetDescr(partRelInfo->ri_RelationDesc));
/*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 071a000..9819e7a 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -831,7 +831,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
*/
void
ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
- Index rti)
+ Index rti, Relation rootTargetDesc)
{
Relation resultRelationDesc;
@@ -839,7 +839,7 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
InitResultRelInfo(resultRelInfo,
resultRelationDesc,
rti,
- NULL,
+ rootTargetDesc,
estate->es_instrument);
if (estate->es_result_relations == NULL)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e0f2428..7cf9a5f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -508,7 +508,8 @@ ExecInsert(ModifyTableState *mtstate,
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
- (resultRelInfo->ri_PartitionRoot == NULL ||
+ (resultRelInfo->ri_RelationDesc ==
+ resultRelInfo->ri_RootTargetDesc ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);
@@ -2216,15 +2217,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
if (node->rootRelation > 0)
{
+ rel = ExecGetRangeTableRelation(estate, node->rootRelation);
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
- node->rootRelation);
+ node->rootRelation, rel);
}
else
{
+ Index rootRelation = linitial_int(node->resultRelations);
+
+ rel = ExecGetRangeTableRelation(estate, rootRelation);
mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
+ rel);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
@@ -2260,7 +2265,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* was initialized already.)
*/
if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+ ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
/* Initialize the usesFdwDirectModify flag */
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
@@ -2337,9 +2342,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
i++;
}
- /* Get the target relation */
- rel = mtstate->rootResultRelInfo->ri_RelationDesc;
-
/*
* If it's not a partitioned table after all, UPDATE tuple routing should
* not be attempted.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 8c7fad8..215d3c7 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1181,7 +1181,7 @@ apply_handle_insert(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
/* Input functions may need an active snapshot, so get one */
PushActiveSnapshot(GetTransactionSnapshot());
@@ -1306,7 +1306,7 @@ apply_handle_update(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
/*
* Populate updatedCols so that per-column triggers can fire. This could
@@ -1462,7 +1462,7 @@ apply_handle_delete(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
PushActiveSnapshot(GetTransactionSnapshot());
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 0c48d2a..2bc349b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -191,7 +191,7 @@ extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- Relation partition_root,
+ Relation rootTargetDesc,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
@@ -551,7 +551,7 @@ exec_rt_fetch(Index rti, EState *estate)
extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
- Index rti);
+ Index rti, Relation rootTargetDesc);
extern int executor_errposition(EState *estate, int location);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 61ba4c3..e6bdf80 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -411,6 +411,9 @@ typedef struct ResultRelInfo
/* relation descriptor for result relation */
Relation ri_RelationDesc;
+ /* relation descriptor of the original target relation */
+ Relation ri_RootTargetDesc;
+
/* # of indices existing on result relation */
int ri_NumIndices;
@@ -487,7 +490,6 @@ typedef struct ResultRelInfo
* ExecInitRoutingInfo, are non-NULL if partition has a different tuple
* format than the root table.
*/
- Relation ri_PartitionRoot;
TupleConversionMap *ri_RootToPartitionMap;
TupleTableSlot *ri_PartitionTupleSlot;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef..42efca0 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2458,7 +2458,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 10).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10');
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (20, 1, 10).
+DETAIL: Failing row contains (10, 1, 20).
-- insert with child not null constraint error
INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL);
ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint
@@ -2468,7 +2468,7 @@ ERROR: null value in column "data" of relation "errtst_child_plaindef" violates
DETAIL: Failing row contains (10, 1, null).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL);
ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint
-DETAIL: Failing row contains (20, 1, null).
+DETAIL: Failing row contains (null, 1, 20).
-- insert with shared check constraint error
INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5');
ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small"
@@ -2478,7 +2478,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 5, 5).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5');
ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small"
-DETAIL: Failing row contains (20, 5, 5).
+DETAIL: Failing row contains (5, 5, 20).
-- within partition update without child check constraint violation
BEGIN;
UPDATE errtst_parent SET data = data + 1 WHERE partid = 0;
@@ -2523,7 +2523,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 15).
UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10;
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (20, 1, 15).
+DETAIL: Failing row contains (15, 1, 20).
UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20;
ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check"
DETAIL: Failing row contains (0, 1, 15).
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index da50ee3..f132d74 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -546,7 +546,7 @@ create trigger mlparted11_trig before insert ON mlparted11
-- to the BR trigger mlparted11_trig_fn)
insert into mlparted values (1, 2);
ERROR: new row for relation "mlparted11" violates check constraint "check_b"
-DETAIL: Failing row contains (1, 4).
+DETAIL: Failing row contains (4, 1).
drop trigger mlparted11_trig on mlparted11;
drop function mlparted11_trig_fn();
-- check that inserting into an internal partition successfully results in
@@ -592,12 +592,12 @@ alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 5
alter table mlparted add constraint check_b check (a = 1 and b < 45);
insert into mlparted values (1, 45, 'a');
ERROR: new row for relation "mlparted5a" violates check constraint "check_b"
-DETAIL: Failing row contains (1, 45, a).
+DETAIL: Failing row contains (1, a, 45).
create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b'; return new; end; $$ language plpgsql;
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
-DETAIL: Failing row contains (b, 1, 40).
+DETAIL: Failing row contains (1, b, 40).
drop table mlparted5;
alter table mlparted drop constraint check_b;
-- Check multi-level default partition
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 2490533..e77a2ee 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2648,7 +2648,7 @@ select tableoid::regclass, * from uv_pt;
create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
insert into uv_ptv_wco values (1, 2);
ERROR: new row violates check option for view "uv_ptv_wco"
-DETAIL: Failing row contains (1, 2, null).
+DETAIL: Failing row contains (2, null, 1).
drop view uv_ptv, uv_ptv_wco;
drop table uv_pt, uv_pt1, uv_pt11;
-- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
@@ -2674,7 +2674,7 @@ with check option;
-- rowtype after tuple-routing
insert into wcowrtest_v2 values (2, 'no such row in sometable');
ERROR: new row violates check option for view "wcowrtest_v2"
-DETAIL: Failing row contains (2, no such row in sometable).
+DETAIL: Failing row contains (no such row in sometable, 2).
drop view wcowrtest_v, wcowrtest_v2;
drop table wcowrtest, sometable;
-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..8819921 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -409,7 +409,7 @@ DETAIL: Failing row contains (a, 4, 120, 1, 1).
-- fail, row movement with check option violation
UPDATE upview set a = 'b', b = 15, c = 120 WHERE b = 4;
ERROR: new row violates check option for view "upview"
-DETAIL: Failing row contains (b, 15, 120, 1, 1).
+DETAIL: Failing row contains (1, 120, 1, b, 15).
-- ok, row movement, check option passes
UPDATE upview set a = 'b', b = 15 WHERE b = 4;
:show_data;
--
1.8.3.1
v11-0003-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v11-0003-Initialize-result-relation-information-lazily.patchDownload
From d54a350d7c48c6f7906ed4a76ab42fde58c46941 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v11 3/3] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also delays the initialization of
ModifyTableState.mt_partition_tuple_routing in the UPDATE case to the
first time ExecCrossPartitionUpdate() is called. That allows us to
get rid of the somewhat convoluted logic used to decide whether
ExecInitModifyTable() should initialize it. Related to that it the
lazy initialization of ri_ChildToRootMap in the ResultRelInfo of the
source partition of a tuple movement operation. Note that there is a
regression test output change in update.out resulting from this change
-- whereas previously a partition constraint violation error would be
reported as occurring on a leaf partition, it is now shown as occurring
on the query's target relation, which is valid because it is really
that table's (which is a sub-partitioned table) partition constraint
that is actually violated in the affected test cases.
While at it, also delay the opening of result relation indices,
ExecOpenIndices(), to the first time ExecInsert() or ExecUpdate() is
called.
---
doc/src/sgml/fdwhandler.sgml | 9 +-
src/backend/commands/explain.c | 12 +-
src/backend/commands/trigger.c | 2 +-
src/backend/executor/execMain.c | 6 +
src/backend/executor/execPartition.c | 106 +++--
src/backend/executor/execUtils.c | 21 +
src/backend/executor/nodeModifyTable.c | 829 +++++++++++++++++----------------
src/include/executor/executor.h | 5 +
src/include/nodes/execnodes.h | 1 +
src/test/regress/expected/update.out | 12 +-
10 files changed, 540 insertions(+), 463 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index b1d7c84..d2ab497 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -520,10 +520,11 @@ BeginForeignModify(ModifyTableState *mtstate,
int eflags);
</programlisting>
- Begin executing a foreign table modification operation. This routine is
- called during executor startup. It should perform any initialization
- needed prior to the actual table modifications. Subsequently,
- <function>ExecForeignInsert</function>, <function>ExecForeignUpdate</function> or
+ Begin executing a foreign table modification operation. This is called
+ right before executing the subplan to fetch the tuples to be modified.
+ It should perform any initialization needed prior to the actual table
+ modifications. Subsequently, <function>ExecForeignInsert</function>,
+ <function>ExecForeignUpdate</function> or
<function>ExecForeignDelete</function> will be called for each tuple to be
inserted, updated, or deleted.
</para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43f9b01..ec79557 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -3654,6 +3655,8 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
int j;
List *idxNames = NIL;
ListCell *lst;
+ ResultRelInfo *firstResultRel;
+ Relation rootTargetDesc;
switch (node->operation)
{
@@ -3675,17 +3678,22 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
break;
}
+ Assert(mtstate->rootResultRelInfo != NULL);
+ rootTargetDesc = mtstate->rootResultRelInfo->ri_RelationDesc;
+ firstResultRel = ExecGetResultRelation(mtstate, 0, rootTargetDesc);
+
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ firstResultRel->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j,
+ rootTargetDesc);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index c336b23..af83f42 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -5459,7 +5459,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = relinfo->ri_ChildToRootMap;
+ TupleConversionMap *map = ExecGetChildToRootMap(relinfo);
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d133a14..5828c01 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1246,6 +1246,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1439,6 +1440,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index f540249..2973ac5 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -508,6 +496,33 @@ ExecFindPartition(ModifyTableState *mtstate,
}
/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ result = ExecGetResultRelation(mtstate, elem->index,
+ proute->partition_root);
+
+ return result;
+}
+
+/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
* partition Oid. We also populate the subplan ResultRelInfo with an
@@ -517,9 +532,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
@@ -530,19 +549,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -563,7 +589,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -599,19 +626,27 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo =
+ ExecGetResultRelation(mtstate, 0, proute->partition_root);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -675,7 +710,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -734,7 +768,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -905,15 +938,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
/*
- * Also, if transition capture is required, store a map to convert tuples
- * from partition's rowtype to the root partition table's.
- */
- if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
- leaf_part_rri->ri_ChildToRootMap =
- convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
-
- /*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9819e7a..769362f 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1223,3 +1223,24 @@ ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo)
return relInfo->ri_ReturningSlot;
}
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+TupleConversionMap *
+ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = resultRelInfo->ri_RootTargetDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7cf9a5f..277dd4c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -180,6 +180,304 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
}
/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+ Relation rootTargetDesc)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ /* Nope, so initialize. */
+ if (resultRelInfo == NULL)
+ {
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ PlanState *subplanstate = mtstate->mt_plans[whichrel];
+ Plan *subplan = subplanstate->plan;
+ bool junk_filter_needed = false;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti, rootTargetDesc);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize JunkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the
+ * tlist. UPDATE and DELETE always need a filter, since there's always
+ * at least one junk attribute present --- no need to look first.
+ * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+ * case of a foreign data wrapper it might be a set of junk attributes
+ * sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an
+ * INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * While at it, also initialize a result relation specific slot that
+ * will be used to copy the plan's output tuples into.
+ */
+ Assert(mtstate->mt_scans[whichrel] == NULL);
+ mtstate->mt_scans[whichrel] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
+/*
* ExecCheckTupleVisible -- verify tuple is visible
*
* It would not be consistent with guarantees of the higher isolation levels to
@@ -404,6 +702,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -1080,7 +1381,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;
@@ -1099,13 +1399,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, mtstate, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1159,7 +1473,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,
@@ -1224,6 +1538,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1338,6 +1655,13 @@ lreplace:;
bool retry;
/*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
+ /*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
* root table, which will re-route it to the correct partition.
@@ -1927,17 +2251,19 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
ItemPointerData tuple_ctid;
HeapTupleData oldtupdata;
HeapTuple oldtuple;
+ Relation rootTargetDesc = node->rootResultRelInfo->ri_RelationDesc;
CHECK_FOR_INTERRUPTS();
@@ -1972,9 +2298,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -1998,17 +2322,27 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL &&
+ bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans))
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+ rootTargetDesc);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2018,6 +2352,17 @@ ExecModifyTable(PlanState *pstate)
}
/*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+ rootTargetDesc);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
+ /*
* Ensure input tuple is the right format for the target relation.
*/
if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
@@ -2173,13 +2518,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2196,11 +2538,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2210,12 +2593,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* - the root partitioned table used for tuple routing.
*
* If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * elsewhere in the plan unless if it's an INSERT and its RT index is
+ * given explicitly in node->rootRelation. Otherwise (i.e. table
+ * inheritance) the target relation is the first relation in the
+ * node->resultRelations list.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
rel = ExecGetRangeTableRelation(estate, node->rootRelation);
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
@@ -2226,14 +2610,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
Index rootRelation = linitial_int(node->resultRelations);
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
rel = ExecGetRangeTableRelation(estate, rootRelation);
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
- rel);
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0, rel);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2247,264 +2635,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
- /*
- * 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 an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- i++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
- /*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
* EvalPlanQual mechanism needs to be told about them. Locate the
@@ -2540,121 +2680,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks[0]);
/*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
* ExecPostprocessPlan. (It'd actually work fine to add the primary
@@ -2684,20 +2709,6 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
- /*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
*/
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 2bc349b..4b79267 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -573,6 +573,7 @@ extern int ExecCleanTargetListLength(List *targetlist);
extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
+extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
/*
* prototypes from functions in execIndexing.c
@@ -616,4 +617,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+ Relation rootTargetDesc);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e6bdf80..960a376 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -499,6 +499,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copyfrom.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 8819921..51f42ab 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
1.8.3.1
On Mon, Dec 7, 2020 at 3:53 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Nov 12, 2020 at 5:04 PM Amit Langote <amitlangote09@gmail.com> wrote:
Attached new 0002 which does these adjustments. I went with
ri_RootTargetDesc to go along with ri_RelationDesc.Also, I have updated the original 0002 (now 0003) to make
GetChildToRootMap() use ri_RootTargetDesc instead of
ModifyTableState.rootResultRelInfo.ri_RelationDesc, so that even
AfterTriggerSaveEvent() can now use that function. This allows us to
avoid having to initialize ri_ChildToRootMap anywhere but inside
GetChildRootMap(), with that long comment defending doing so. :-)These needed to be rebased due to recent copy.c upheavals. Attached.
Needed to be rebased again.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v12-0001-Set-ForeignScanState.resultRelInfo-lazily.patchapplication/octet-stream; name=v12-0001-Set-ForeignScanState.resultRelInfo-lazily.patchDownload
From fae19a3ff07023a9b4b95265862e11db287c36a4 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v12 1/3] Set ForeignScanState.resultRelInfo lazily
Instead of doing it in ExecInitForeignScan(), do it on the first
ForeignNext() call. This also moves the BeginDirectModify() call
into ForeignNext().
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily, that is as it begins executing,
instead of in ExecInitModifyTable().
---
doc/src/sgml/fdwhandler.sgml | 7 +++--
src/backend/executor/nodeForeignscan.c | 39 ++++++++++++++++++--------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 9c9293414c..b1d7c84002 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -890,9 +890,10 @@ BeginDirectModify(ForeignScanState *node,
</programlisting>
Prepare to execute a direct modification on the remote server.
- This is called during executor startup. It should perform any
- initialization needed prior to the direct modification (that should be
- done upon the first call to <function>IterateDirectModify</function>).
+ This is called right before the first time <function>IterateDirectModify</function>
+ is called on the node. It should perform any initialization needed prior to the
+ direct modification (that should be done upon the first call to
+ <function>IterateDirectModify</function>).
The <structname>ForeignScanState</structname> node has already been created, but
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0b20f94035..5259abf730 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -49,7 +49,31 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
+ {
+ /*
+ * For FDW's convenience, look up the result relation info and set
+ * ForeignScanState.resultRelInfo if not already done. This is also
+ * a good time to call BeginDirectModify().
+ */
+ Assert(plan->resultRelation > 0);
+ if (node->resultRelInfo == NULL)
+ {
+ EState *estate = node->ss.ps.state;
+ ResultRelInfo *rInfo = estate->es_result_relations[plan->resultRelation - 1];
+
+ /* ExecInitModifyTable() must have initialized one already. */
+ Assert(rInfo != NULL);
+ node->resultRelInfo = rInfo;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ Assert(rInfo->ri_FdwRoutine != NULL &&
+ rInfo->ri_FdwRoutine->BeginDirectModify != NULL);
+ rInfo->ri_FdwRoutine->BeginDirectModify(node,
+ estate->es_top_eflags);
+ MemoryContextSwitchTo(oldcontext);
+ }
slot = node->fdwroutine->IterateDirectModify(node);
+ }
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@@ -215,24 +239,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, any
+ * additional initializations are performed right before calling
+ * IterateDirectModify() for the first time.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
--
2.24.1
v12-0002-Rethink-ResultRelInfo.ri_PartitionRoot.patchapplication/octet-stream; name=v12-0002-Rethink-ResultRelInfo.ri_PartitionRoot.patchDownload
From 13ee7d66b9c0bc9a16252c163bfcfda06728a2b2 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 12 Nov 2020 15:04:02 +0900
Subject: [PATCH v12 2/3] Rethink ResultRelInfo.ri_PartitionRoot
Its current usage is specific to partition tuple routing, although
there are other places that could use it do determine the original
target relation of the query without having access to the
ModifyTableState. So set it in all result relations, not just
those that are targets of tuple routing. While at it, also rename
the field to ri_RootTargetDesc to denote its wider scope.
This also removes the many instances of a stanza to convert a routed
tuple in the partition's format into the format of the original target
relation of the query in favor of just showing the partition format
tuple in the error messages, which seems harmless, but wasn't thought
to be so when the code was originally written.
---
src/backend/commands/copyfrom.c | 2 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/executor/execMain.c | 181 +++---------------
src/backend/executor/execPartition.c | 11 +-
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 18 +-
src/backend/replication/logical/worker.c | 6 +-
src/include/executor/executor.h | 4 +-
src/include/nodes/execnodes.h | 4 +-
src/test/regress/expected/inherit.out | 8 +-
src/test/regress/expected/insert.out | 6 +-
src/test/regress/expected/updatable_views.out | 4 +-
src/test/regress/expected/update.out | 2 +-
13 files changed, 61 insertions(+), 191 deletions(-)
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a6eb..804526e80e 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -648,7 +648,7 @@ CopyFrom(CopyFromState cstate)
*/
ExecInitRangeTable(estate, cstate->range_table);
resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
- ExecInitResultRelation(estate, resultRelInfo, 1);
+ ExecInitResultRelation(estate, resultRelInfo, 1, cstate->rel);
/* Verify the named relation is a valid target for INSERT */
CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1fa9f19f08..e52f74413a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1804,7 +1804,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
- NULL,
+ rel,
0);
estate->es_opened_result_relations =
lappend(estate->es_opened_result_relations, resultRelInfo);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f589f9..d133a14f61 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -93,11 +93,10 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
Bitmapset *modifiedCols,
AclMode requiredPerms);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
- TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
- int maxfieldlen);
+static char *ExecBuildSlotValueDescription(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot,
+ int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
/*
@@ -1196,13 +1195,14 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- Relation partition_root,
+ Relation rootTargetDesc,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
resultRelInfo->type = T_ResultRelInfo;
resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
resultRelInfo->ri_RelationDesc = resultRelationDesc;
+ resultRelInfo->ri_RootTargetDesc = rootTargetDesc;
resultRelInfo->ri_NumIndices = 0;
resultRelInfo->ri_IndexRelationDescs = NULL;
resultRelInfo->ri_IndexRelationInfo = NULL;
@@ -1242,7 +1242,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ReturningSlot = NULL;
resultRelInfo->ri_TrigOldSlot = NULL;
resultRelInfo->ri_TrigNewSlot = NULL;
- resultRelInfo->ri_PartitionRoot = partition_root;
resultRelInfo->ri_RootToPartitionMap = NULL; /* set by
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
@@ -1321,7 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
- NULL,
+ rel,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -1733,50 +1732,11 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
EState *estate)
{
- Oid root_relid;
- TupleDesc tupdesc;
char *val_desc;
- Bitmapset *modifiedCols;
-
- /*
- * If the tuple has been routed, it's been converted to the partition's
- * rowtype, which might differ from the root table's. We must convert it
- * back to the root table's rowtype so that val_desc in the error message
- * matches the input tuple.
- */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc;
- AttrMap *map;
-
- root_relid = RelationGetRelid(resultRelInfo->ri_PartitionRoot);
- tupdesc = RelationGetDescr(resultRelInfo->ri_PartitionRoot);
-
- old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so allocate a
- * new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
- else
- {
- root_relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
- tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- }
-
- modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
- GetUpdatedColumns(resultRelInfo, estate));
- val_desc = ExecBuildSlotValueDescription(root_relid,
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
@@ -1804,9 +1764,6 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
TupleConstr *constr = tupdesc->constr;
- Bitmapset *modifiedCols;
- Bitmapset *insertedCols;
- Bitmapset *updatedCols;
Assert(constr); /* we should not be called otherwise */
@@ -1821,52 +1778,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
if (att->attnotnull && slot_attisnull(slot, attrChk))
{
- char *val_desc;
- Relation orig_rel = rel;
- TupleDesc orig_tupdesc = RelationGetDescr(rel);
-
- /*
- * If the tuple has been routed, it's been converted to the
- * partition's rowtype, which might differ from the root
- * table's. We must convert it back to the root table's
- * rowtype so that val_desc shown error message matches the
- * input tuple.
- */
- if (resultRelInfo->ri_PartitionRoot)
- {
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(orig_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so
- * allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
+ char *val_desc;
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" of relation \"%s\" violates not-null constraint",
NameStr(att->attname),
- RelationGetRelationName(orig_rel)),
+ RelationGetRelationName(rel)),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
- errtablecol(orig_rel, attrChk)));
+ errtablecol(rel, attrChk)));
}
}
}
@@ -1878,43 +1803,16 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
{
char *val_desc;
- Relation orig_rel = rel;
- /* See the comment above. */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc = RelationGetDescr(rel);
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so
- * allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate, resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
- RelationGetRelationName(orig_rel), failed),
+ RelationGetRelationName(rel), failed),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
- errtableconstraint(orig_rel, failed)));
+ errtableconstraint(rel, failed)));
}
}
}
@@ -1932,8 +1830,6 @@ void
ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
{
- Relation rel = resultRelInfo->ri_RelationDesc;
- TupleDesc tupdesc = RelationGetDescr(rel);
ExprContext *econtext;
ListCell *l1,
*l2;
@@ -1971,9 +1867,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
if (!ExecQual(wcoExpr, econtext))
{
char *val_desc;
- Bitmapset *modifiedCols;
- Bitmapset *insertedCols;
- Bitmapset *updatedCols;
switch (wco->kind)
{
@@ -1987,34 +1880,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* USING policy.
*/
case WCO_VIEW_CHECK:
- /* See the comment in ExecConstraints(). */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc = RelationGetDescr(rel);
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed,
- * so allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
@@ -2077,10 +1945,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* columns they are.
*/
static char *
-ExecBuildSlotValueDescription(Oid reloid,
+ExecBuildSlotValueDescription(EState *estate,
+ ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
int maxfieldlen)
{
StringInfoData buf;
@@ -2091,6 +1958,9 @@ ExecBuildSlotValueDescription(Oid reloid,
AclResult aclresult;
bool table_perm = false;
bool any_perm = false;
+ Oid reloid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ TupleDesc tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+ Bitmapset *modifiedCols;
/*
* Check if RLS is enabled and should be active for the relation; if so,
@@ -2100,6 +1970,9 @@ ExecBuildSlotValueDescription(Oid reloid,
if (check_enable_rls(reloid, InvalidOid, true) == RLS_ENABLED)
return NULL;
+ modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
+ GetUpdatedColumns(resultRelInfo, estate));
+
initStringInfo(&buf);
appendStringInfoChar(&buf, '(');
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 97bfc8bd71..50ed83e4c8 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -541,13 +541,6 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
}
}
@@ -917,7 +910,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
+ RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
@@ -961,7 +954,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
* partition from the parent's type to the partition's.
*/
partRelInfo->ri_RootToPartitionMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RootTargetDesc),
RelationGetDescr(partRelInfo->ri_RelationDesc));
/*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 071a0007eb..9819e7ae6f 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -831,7 +831,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
*/
void
ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
- Index rti)
+ Index rti, Relation rootTargetDesc)
{
Relation resultRelationDesc;
@@ -839,7 +839,7 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
InitResultRelInfo(resultRelInfo,
resultRelationDesc,
rti,
- NULL,
+ rootTargetDesc,
estate->es_instrument);
if (estate->es_result_relations == NULL)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ab3d655e60..3e325435ba 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -508,7 +508,8 @@ ExecInsert(ModifyTableState *mtstate,
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
- (resultRelInfo->ri_PartitionRoot == NULL ||
+ (resultRelInfo->ri_RelationDesc ==
+ resultRelInfo->ri_RootTargetDesc ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);
@@ -2216,15 +2217,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
if (node->rootRelation > 0)
{
+ rel = ExecGetRangeTableRelation(estate, node->rootRelation);
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
- node->rootRelation);
+ node->rootRelation, rel);
}
else
{
+ Index rootRelation = linitial_int(node->resultRelations);
+
+ rel = ExecGetRangeTableRelation(estate, rootRelation);
mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
+ rel);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
@@ -2260,7 +2265,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* was initialized already.)
*/
if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+ ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
/* Initialize the usesFdwDirectModify flag */
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
@@ -2337,9 +2342,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
i++;
}
- /* Get the target relation */
- rel = mtstate->rootResultRelInfo->ri_RelationDesc;
-
/*
* If it's not a partitioned table after all, UPDATE tuple routing should
* not be attempted.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 3874939380..2720375e40 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1181,7 +1181,7 @@ apply_handle_insert(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
/* Input functions may need an active snapshot, so get one */
PushActiveSnapshot(GetTransactionSnapshot());
@@ -1306,7 +1306,7 @@ apply_handle_update(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
/*
* Populate updatedCols so that per-column triggers can fire. This could
@@ -1462,7 +1462,7 @@ apply_handle_delete(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
PushActiveSnapshot(GetTransactionSnapshot());
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 0c48d2a519..2bc349b782 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -191,7 +191,7 @@ extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- Relation partition_root,
+ Relation rootTargetDesc,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
@@ -551,7 +551,7 @@ exec_rt_fetch(Index rti, EState *estate)
extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
- Index rti);
+ Index rti, Relation rootTargetDesc);
extern int executor_errposition(EState *estate, int location);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 61ba4c3666..e6bdf80555 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -411,6 +411,9 @@ typedef struct ResultRelInfo
/* relation descriptor for result relation */
Relation ri_RelationDesc;
+ /* relation descriptor of the original target relation */
+ Relation ri_RootTargetDesc;
+
/* # of indices existing on result relation */
int ri_NumIndices;
@@ -487,7 +490,6 @@ typedef struct ResultRelInfo
* ExecInitRoutingInfo, are non-NULL if partition has a different tuple
* format than the root table.
*/
- Relation ri_PartitionRoot;
TupleConversionMap *ri_RootToPartitionMap;
TupleTableSlot *ri_PartitionTupleSlot;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef654..42efca0581 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2458,7 +2458,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 10).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10');
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (20, 1, 10).
+DETAIL: Failing row contains (10, 1, 20).
-- insert with child not null constraint error
INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL);
ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint
@@ -2468,7 +2468,7 @@ ERROR: null value in column "data" of relation "errtst_child_plaindef" violates
DETAIL: Failing row contains (10, 1, null).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL);
ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint
-DETAIL: Failing row contains (20, 1, null).
+DETAIL: Failing row contains (null, 1, 20).
-- insert with shared check constraint error
INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5');
ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small"
@@ -2478,7 +2478,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 5, 5).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5');
ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small"
-DETAIL: Failing row contains (20, 5, 5).
+DETAIL: Failing row contains (5, 5, 20).
-- within partition update without child check constraint violation
BEGIN;
UPDATE errtst_parent SET data = data + 1 WHERE partid = 0;
@@ -2523,7 +2523,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 15).
UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10;
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (20, 1, 15).
+DETAIL: Failing row contains (15, 1, 20).
UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20;
ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check"
DETAIL: Failing row contains (0, 1, 15).
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index da50ee3b67..f132d7458b 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -546,7 +546,7 @@ create trigger mlparted11_trig before insert ON mlparted11
-- to the BR trigger mlparted11_trig_fn)
insert into mlparted values (1, 2);
ERROR: new row for relation "mlparted11" violates check constraint "check_b"
-DETAIL: Failing row contains (1, 4).
+DETAIL: Failing row contains (4, 1).
drop trigger mlparted11_trig on mlparted11;
drop function mlparted11_trig_fn();
-- check that inserting into an internal partition successfully results in
@@ -592,12 +592,12 @@ alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 5
alter table mlparted add constraint check_b check (a = 1 and b < 45);
insert into mlparted values (1, 45, 'a');
ERROR: new row for relation "mlparted5a" violates check constraint "check_b"
-DETAIL: Failing row contains (1, 45, a).
+DETAIL: Failing row contains (1, a, 45).
create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b'; return new; end; $$ language plpgsql;
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
-DETAIL: Failing row contains (b, 1, 40).
+DETAIL: Failing row contains (1, b, 40).
drop table mlparted5;
alter table mlparted drop constraint check_b;
-- Check multi-level default partition
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 24905332b1..e77a2ee41c 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2648,7 +2648,7 @@ select tableoid::regclass, * from uv_pt;
create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
insert into uv_ptv_wco values (1, 2);
ERROR: new row violates check option for view "uv_ptv_wco"
-DETAIL: Failing row contains (1, 2, null).
+DETAIL: Failing row contains (2, null, 1).
drop view uv_ptv, uv_ptv_wco;
drop table uv_pt, uv_pt1, uv_pt11;
-- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
@@ -2674,7 +2674,7 @@ with check option;
-- rowtype after tuple-routing
insert into wcowrtest_v2 values (2, 'no such row in sometable');
ERROR: new row violates check option for view "wcowrtest_v2"
-DETAIL: Failing row contains (2, no such row in sometable).
+DETAIL: Failing row contains (no such row in sometable, 2).
drop view wcowrtest_v, wcowrtest_v2;
drop table wcowrtest, sometable;
-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d79f6..8819921d0a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -409,7 +409,7 @@ DETAIL: Failing row contains (a, 4, 120, 1, 1).
-- fail, row movement with check option violation
UPDATE upview set a = 'b', b = 15, c = 120 WHERE b = 4;
ERROR: new row violates check option for view "upview"
-DETAIL: Failing row contains (b, 15, 120, 1, 1).
+DETAIL: Failing row contains (1, 120, 1, b, 15).
-- ok, row movement, check option passes
UPDATE upview set a = 'b', b = 15 WHERE b = 4;
:show_data;
--
2.24.1
v12-0003-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v12-0003-Initialize-result-relation-information-lazily.patchDownload
From 3e3e3674fdfd98c72630436564834acbf6e2f23a Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v12 3/3] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also delays the initialization of
ModifyTableState.mt_partition_tuple_routing in the UPDATE case to the
first time ExecCrossPartitionUpdate() is called. That allows us to
get rid of the somewhat convoluted logic used to decide whether
ExecInitModifyTable() should initialize it. Related to that it the
lazy initialization of ri_ChildToRootMap in the ResultRelInfo of the
source partition of a tuple movement operation. Note that there is a
regression test output change in update.out resulting from this change
-- whereas previously a partition constraint violation error would be
reported as occurring on a leaf partition, it is now shown as occurring
on the query's target relation, which is valid because it is really
that table's (which is a sub-partitioned table) partition constraint
that is actually violated in the affected test cases.
While at it, also delay the opening of result relation indices,
ExecOpenIndices(), to the first time ExecInsert() or ExecUpdate() is
called.
---
doc/src/sgml/fdwhandler.sgml | 9 +-
src/backend/commands/explain.c | 12 +-
src/backend/commands/trigger.c | 2 +-
src/backend/executor/execMain.c | 6 +
src/backend/executor/execPartition.c | 106 ++--
src/backend/executor/execUtils.c | 21 +
src/backend/executor/nodeModifyTable.c | 827 +++++++++++++------------
src/include/executor/executor.h | 5 +
src/include/nodes/execnodes.h | 1 +
src/test/regress/expected/update.out | 12 +-
10 files changed, 540 insertions(+), 461 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index b1d7c84002..d2ab4977e9 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -520,10 +520,11 @@ BeginForeignModify(ModifyTableState *mtstate,
int eflags);
</programlisting>
- Begin executing a foreign table modification operation. This routine is
- called during executor startup. It should perform any initialization
- needed prior to the actual table modifications. Subsequently,
- <function>ExecForeignInsert</function>, <function>ExecForeignUpdate</function> or
+ Begin executing a foreign table modification operation. This is called
+ right before executing the subplan to fetch the tuples to be modified.
+ It should perform any initialization needed prior to the actual table
+ modifications. Subsequently, <function>ExecForeignInsert</function>,
+ <function>ExecForeignUpdate</function> or
<function>ExecForeignDelete</function> will be called for each tuple to be
inserted, updated, or deleted.
</para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43f9b01e83..ec79557e40 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -3654,6 +3655,8 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
int j;
List *idxNames = NIL;
ListCell *lst;
+ ResultRelInfo *firstResultRel;
+ Relation rootTargetDesc;
switch (node->operation)
{
@@ -3675,17 +3678,22 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
break;
}
+ Assert(mtstate->rootResultRelInfo != NULL);
+ rootTargetDesc = mtstate->rootResultRelInfo->ri_RelationDesc;
+ firstResultRel = ExecGetResultRelation(mtstate, 0, rootTargetDesc);
+
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ firstResultRel->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j,
+ rootTargetDesc);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index c336b238aa..af83f42d52 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -5459,7 +5459,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = relinfo->ri_ChildToRootMap;
+ TupleConversionMap *map = ExecGetChildToRootMap(relinfo);
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d133a14f61..5828c01505 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1246,6 +1246,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1439,6 +1440,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 50ed83e4c8..1e4973b42a 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -507,6 +495,33 @@ ExecFindPartition(ModifyTableState *mtstate,
return rri;
}
+/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ result = ExecGetResultRelation(mtstate, elem->index,
+ proute->partition_root);
+
+ return result;
+}
+
/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
@@ -517,9 +532,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(SubplanResultRelHashElem);
@@ -529,19 +548,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -562,7 +588,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -598,19 +625,27 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo =
+ ExecGetResultRelation(mtstate, 0, proute->partition_root);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -674,7 +709,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -733,7 +767,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -903,15 +936,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
}
- /*
- * Also, if transition capture is required, store a map to convert tuples
- * from partition's rowtype to the root partition table's.
- */
- if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
- leaf_part_rri->ri_ChildToRootMap =
- convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
-
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9819e7ae6f..769362f0e6 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1223,3 +1223,24 @@ ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo)
return relInfo->ri_ReturningSlot;
}
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+TupleConversionMap *
+ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = resultRelInfo->ri_RootTargetDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 3e325435ba..277dd4c49a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -179,6 +179,304 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
return ExecProject(projectReturning);
}
+/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+ Relation rootTargetDesc)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ /* Nope, so initialize. */
+ if (resultRelInfo == NULL)
+ {
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ PlanState *subplanstate = mtstate->mt_plans[whichrel];
+ Plan *subplan = subplanstate->plan;
+ bool junk_filter_needed = false;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti, rootTargetDesc);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize JunkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the
+ * tlist. UPDATE and DELETE always need a filter, since there's always
+ * at least one junk attribute present --- no need to look first.
+ * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+ * case of a foreign data wrapper it might be a set of junk attributes
+ * sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an
+ * INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * While at it, also initialize a result relation specific slot that
+ * will be used to copy the plan's output tuples into.
+ */
+ Assert(mtstate->mt_scans[whichrel] == NULL);
+ mtstate->mt_scans[whichrel] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
/*
* ExecCheckTupleVisible -- verify tuple is visible
*
@@ -404,6 +702,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -1080,7 +1381,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;
@@ -1099,13 +1399,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, mtstate, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1159,7 +1473,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,
@@ -1224,6 +1538,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1337,6 +1654,13 @@ lreplace:;
*retry_slot;
bool retry;
+ /*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
/*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
@@ -1927,17 +2251,19 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
ItemPointerData tuple_ctid;
HeapTupleData oldtupdata;
HeapTuple oldtuple;
+ Relation rootTargetDesc = node->rootResultRelInfo->ri_RelationDesc;
CHECK_FOR_INTERRUPTS();
@@ -1972,9 +2298,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -1998,17 +2322,27 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL &&
+ bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans))
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+ rootTargetDesc);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2017,6 +2351,17 @@ ExecModifyTable(PlanState *pstate)
break;
}
+ /*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+ rootTargetDesc);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
/*
* Ensure input tuple is the right format for the target relation.
*/
@@ -2173,13 +2518,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2196,11 +2538,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2210,12 +2593,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* - the root partitioned table used for tuple routing.
*
* If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * elsewhere in the plan unless if it's an INSERT and its RT index is
+ * given explicitly in node->rootRelation. Otherwise (i.e. table
+ * inheritance) the target relation is the first relation in the
+ * node->resultRelations list.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
rel = ExecGetRangeTableRelation(estate, node->rootRelation);
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
@@ -2226,14 +2610,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
Index rootRelation = linitial_int(node->resultRelations);
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
rel = ExecGetRangeTableRelation(estate, rootRelation);
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
- rel);
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0, rel);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2247,261 +2635,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
- /*
- * 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 an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
/*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
@@ -2537,121 +2679,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
mtstate->mt_arowmarks[0]);
- /*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
/*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
@@ -2681,20 +2708,6 @@ ExecEndModifyTable(ModifyTableState *node)
{
int i;
- /*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
/*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 2bc349b782..4b79267e1e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -573,6 +573,7 @@ extern int ExecCleanTargetListLength(List *targetlist);
extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
+extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
/*
* prototypes from functions in execIndexing.c
@@ -616,4 +617,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+ Relation rootTargetDesc);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e6bdf80555..960a376d11 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -499,6 +499,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copyfrom.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 8819921d0a..51f42abf2a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
2.24.1
On Tue, Dec 22, 2020 at 5:16 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Mon, Dec 7, 2020 at 3:53 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Nov 12, 2020 at 5:04 PM Amit Langote <amitlangote09@gmail.com> wrote:
Attached new 0002 which does these adjustments. I went with
ri_RootTargetDesc to go along with ri_RelationDesc.Also, I have updated the original 0002 (now 0003) to make
GetChildToRootMap() use ri_RootTargetDesc instead of
ModifyTableState.rootResultRelInfo.ri_RelationDesc, so that even
AfterTriggerSaveEvent() can now use that function. This allows us to
avoid having to initialize ri_ChildToRootMap anywhere but inside
GetChildRootMap(), with that long comment defending doing so. :-)These needed to be rebased due to recent copy.c upheavals. Attached.
Needed to be rebased again.
And again, this time over the recent batch insert API related patches.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v13-0001-Set-ForeignScanState.resultRelInfo-lazily.patchapplication/octet-stream; name=v13-0001-Set-ForeignScanState.resultRelInfo-lazily.patchDownload
From 37f49d35a25f41ede6404c02af21aaa7e7a7de3d Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v13 1/3] Set ForeignScanState.resultRelInfo lazily
Instead of doing it in ExecInitForeignScan(), do it on the first
ForeignNext() call. This also moves the BeginDirectModify() call
into ForeignNext().
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily, that is as it begins executing,
instead of in ExecInitModifyTable().
---
doc/src/sgml/fdwhandler.sgml | 7 +++--
src/backend/executor/nodeForeignscan.c | 39 ++++++++++++++++++--------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 854913ae5f..14d2d923de 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -967,9 +967,10 @@ BeginDirectModify(ForeignScanState *node,
</programlisting>
Prepare to execute a direct modification on the remote server.
- This is called during executor startup. It should perform any
- initialization needed prior to the direct modification (that should be
- done upon the first call to <function>IterateDirectModify</function>).
+ This is called right before the first time <function>IterateDirectModify</function>
+ is called on the node. It should perform any initialization needed prior to the
+ direct modification (that should be done upon the first call to
+ <function>IterateDirectModify</function>).
The <structname>ForeignScanState</structname> node has already been created, but
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0969e53c3a..25f6fd0e49 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -49,7 +49,31 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
+ {
+ /*
+ * For FDW's convenience, look up the result relation info and set
+ * ForeignScanState.resultRelInfo if not already done. This is also
+ * a good time to call BeginDirectModify().
+ */
+ Assert(plan->resultRelation > 0);
+ if (node->resultRelInfo == NULL)
+ {
+ EState *estate = node->ss.ps.state;
+ ResultRelInfo *rInfo = estate->es_result_relations[plan->resultRelation - 1];
+
+ /* ExecInitModifyTable() must have initialized one already. */
+ Assert(rInfo != NULL);
+ node->resultRelInfo = rInfo;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ Assert(rInfo->ri_FdwRoutine != NULL &&
+ rInfo->ri_FdwRoutine->BeginDirectModify != NULL);
+ rInfo->ri_FdwRoutine->BeginDirectModify(node,
+ estate->es_top_eflags);
+ MemoryContextSwitchTo(oldcontext);
+ }
slot = node->fdwroutine->IterateDirectModify(node);
+ }
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@@ -215,24 +239,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, any
+ * additional initializations are performed right before calling
+ * IterateDirectModify() for the first time.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
--
2.24.1
v13-0002-Rethink-ResultRelInfo.ri_PartitionRoot.patchapplication/octet-stream; name=v13-0002-Rethink-ResultRelInfo.ri_PartitionRoot.patchDownload
From ce23850f63ce3eea4d98d7bb1d7b91b4bb81e1a4 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 12 Nov 2020 15:04:02 +0900
Subject: [PATCH v13 2/3] Rethink ResultRelInfo.ri_PartitionRoot
Its current usage is specific to partition tuple routing, although
there are other places that could use it do determine the original
target relation of the query without having access to the
ModifyTableState. So set it in all result relations, not just
those that are targets of tuple routing. While at it, also rename
the field to ri_RootTargetDesc to denote its wider scope.
This also removes the many instances of a stanza to convert a routed
tuple in the partition's format into the format of the original target
relation of the query in favor of just showing the partition format
tuple in the error messages, which seems harmless, but wasn't thought
to be so when the code was originally written.
---
src/backend/commands/copyfrom.c | 2 +-
src/backend/commands/tablecmds.c | 2 +-
src/backend/executor/execMain.c | 181 +++---------------
src/backend/executor/execPartition.c | 11 +-
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeModifyTable.c | 18 +-
src/backend/replication/logical/worker.c | 6 +-
src/include/executor/executor.h | 4 +-
src/include/nodes/execnodes.h | 4 +-
src/test/regress/expected/inherit.out | 8 +-
src/test/regress/expected/insert.out | 6 +-
src/test/regress/expected/updatable_views.out | 4 +-
src/test/regress/expected/update.out | 2 +-
13 files changed, 61 insertions(+), 191 deletions(-)
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index c39cc736ed..868d0fd3ea 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -650,7 +650,7 @@ CopyFrom(CopyFromState cstate)
*/
ExecInitRangeTable(estate, cstate->range_table);
resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
- ExecInitResultRelation(estate, resultRelInfo, 1);
+ ExecInitResultRelation(estate, resultRelInfo, 1, cstate->rel);
/* Verify the named relation is a valid target for INSERT */
CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..cf4c645597 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1804,7 +1804,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
- NULL,
+ rel,
0);
estate->es_opened_result_relations =
lappend(estate->es_opened_result_relations, resultRelInfo);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index f4dd47acc7..8eb05530e5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -93,11 +93,10 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
Bitmapset *modifiedCols,
AclMode requiredPerms);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
- TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
- int maxfieldlen);
+static char *ExecBuildSlotValueDescription(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot,
+ int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
/*
@@ -1196,13 +1195,14 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- Relation partition_root,
+ Relation rootTargetDesc,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
resultRelInfo->type = T_ResultRelInfo;
resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
resultRelInfo->ri_RelationDesc = resultRelationDesc;
+ resultRelInfo->ri_RootTargetDesc = rootTargetDesc;
resultRelInfo->ri_NumIndices = 0;
resultRelInfo->ri_IndexRelationDescs = NULL;
resultRelInfo->ri_IndexRelationInfo = NULL;
@@ -1242,7 +1242,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ReturningSlot = NULL;
resultRelInfo->ri_TrigOldSlot = NULL;
resultRelInfo->ri_TrigNewSlot = NULL;
- resultRelInfo->ri_PartitionRoot = partition_root;
resultRelInfo->ri_RootToPartitionMap = NULL; /* set by
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
@@ -1321,7 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
- NULL,
+ rel,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -1733,50 +1732,11 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
EState *estate)
{
- Oid root_relid;
- TupleDesc tupdesc;
char *val_desc;
- Bitmapset *modifiedCols;
-
- /*
- * If the tuple has been routed, it's been converted to the partition's
- * rowtype, which might differ from the root table's. We must convert it
- * back to the root table's rowtype so that val_desc in the error message
- * matches the input tuple.
- */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc;
- AttrMap *map;
-
- root_relid = RelationGetRelid(resultRelInfo->ri_PartitionRoot);
- tupdesc = RelationGetDescr(resultRelInfo->ri_PartitionRoot);
-
- old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so allocate a
- * new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
- else
- {
- root_relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
- tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- }
-
- modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
- GetUpdatedColumns(resultRelInfo, estate));
- val_desc = ExecBuildSlotValueDescription(root_relid,
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
@@ -1804,9 +1764,6 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
TupleConstr *constr = tupdesc->constr;
- Bitmapset *modifiedCols;
- Bitmapset *insertedCols;
- Bitmapset *updatedCols;
Assert(constr); /* we should not be called otherwise */
@@ -1821,52 +1778,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
if (att->attnotnull && slot_attisnull(slot, attrChk))
{
- char *val_desc;
- Relation orig_rel = rel;
- TupleDesc orig_tupdesc = RelationGetDescr(rel);
-
- /*
- * If the tuple has been routed, it's been converted to the
- * partition's rowtype, which might differ from the root
- * table's. We must convert it back to the root table's
- * rowtype so that val_desc shown error message matches the
- * input tuple.
- */
- if (resultRelInfo->ri_PartitionRoot)
- {
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(orig_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so
- * allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
+ char *val_desc;
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" of relation \"%s\" violates not-null constraint",
NameStr(att->attname),
- RelationGetRelationName(orig_rel)),
+ RelationGetRelationName(rel)),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
- errtablecol(orig_rel, attrChk)));
+ errtablecol(rel, attrChk)));
}
}
}
@@ -1878,43 +1803,16 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
{
char *val_desc;
- Relation orig_rel = rel;
- /* See the comment above. */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc = RelationGetDescr(rel);
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed, so
- * allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate, resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
- RelationGetRelationName(orig_rel), failed),
+ RelationGetRelationName(rel), failed),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
- errtableconstraint(orig_rel, failed)));
+ errtableconstraint(rel, failed)));
}
}
}
@@ -1932,8 +1830,6 @@ void
ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
{
- Relation rel = resultRelInfo->ri_RelationDesc;
- TupleDesc tupdesc = RelationGetDescr(rel);
ExprContext *econtext;
ListCell *l1,
*l2;
@@ -1971,9 +1867,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
if (!ExecQual(wcoExpr, econtext))
{
char *val_desc;
- Bitmapset *modifiedCols;
- Bitmapset *insertedCols;
- Bitmapset *updatedCols;
switch (wco->kind)
{
@@ -1987,34 +1880,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* USING policy.
*/
case WCO_VIEW_CHECK:
- /* See the comment in ExecConstraints(). */
- if (resultRelInfo->ri_PartitionRoot)
- {
- TupleDesc old_tupdesc = RelationGetDescr(rel);
- AttrMap *map;
-
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- /* a reverse map */
- map = build_attrmap_by_name_if_req(old_tupdesc,
- tupdesc);
-
- /*
- * Partition-specific slot's tupdesc can't be changed,
- * so allocate a new one.
- */
- if (map != NULL)
- slot = execute_attr_map_slot(map, slot,
- MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
- }
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ val_desc = ExecBuildSlotValueDescription(estate,
+ resultRelInfo,
slot,
- tupdesc,
- modifiedCols,
64);
ereport(ERROR,
@@ -2077,10 +1945,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* columns they are.
*/
static char *
-ExecBuildSlotValueDescription(Oid reloid,
+ExecBuildSlotValueDescription(EState *estate,
+ ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
int maxfieldlen)
{
StringInfoData buf;
@@ -2091,6 +1958,9 @@ ExecBuildSlotValueDescription(Oid reloid,
AclResult aclresult;
bool table_perm = false;
bool any_perm = false;
+ Oid reloid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ TupleDesc tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+ Bitmapset *modifiedCols;
/*
* Check if RLS is enabled and should be active for the relation; if so,
@@ -2100,6 +1970,9 @@ ExecBuildSlotValueDescription(Oid reloid,
if (check_enable_rls(reloid, InvalidOid, true) == RLS_ENABLED)
return NULL;
+ modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
+ GetUpdatedColumns(resultRelInfo, estate));
+
initStringInfo(&buf);
appendStringInfoChar(&buf, '(');
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 1746cb8793..088b0760d5 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -541,13 +541,6 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_PartitionRoot = proute->partition_root;
}
}
@@ -917,7 +910,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
+ RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
@@ -961,7 +954,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
* partition from the parent's type to the partition's.
*/
partRelInfo->ri_RootToPartitionMap =
- convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RootTargetDesc),
RelationGetDescr(partRelInfo->ri_RelationDesc));
/*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d84fbaded9..7f248aa6f3 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -831,7 +831,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
*/
void
ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
- Index rti)
+ Index rti, Relation rootTargetDesc)
{
Relation resultRelationDesc;
@@ -839,7 +839,7 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
InitResultRelInfo(resultRelInfo,
resultRelationDesc,
rti,
- NULL,
+ rootTargetDesc,
estate->es_instrument);
if (estate->es_result_relations == NULL)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5d90337498..3b218b57b7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -565,7 +565,8 @@ ExecInsert(ModifyTableState *mtstate,
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
- (resultRelInfo->ri_PartitionRoot == NULL ||
+ (resultRelInfo->ri_RelationDesc ==
+ resultRelInfo->ri_RootTargetDesc ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);
@@ -2359,15 +2360,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
if (node->rootRelation > 0)
{
+ rel = ExecGetRangeTableRelation(estate, node->rootRelation);
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
- node->rootRelation);
+ node->rootRelation, rel);
}
else
{
+ Index rootRelation = linitial_int(node->resultRelations);
+
+ rel = ExecGetRangeTableRelation(estate, rootRelation);
mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
+ rel);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
@@ -2403,7 +2408,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* was initialized already.)
*/
if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+ ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
/* Initialize the usesFdwDirectModify flag */
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
@@ -2480,9 +2485,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
i++;
}
- /* Get the target relation */
- rel = mtstate->rootResultRelInfo->ri_RelationDesc;
-
/*
* If it's not a partitioned table after all, UPDATE tuple routing should
* not be attempted.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index eb7db89cef..9304172657 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1181,7 +1181,7 @@ apply_handle_insert(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
/* Input functions may need an active snapshot, so get one */
PushActiveSnapshot(GetTransactionSnapshot());
@@ -1306,7 +1306,7 @@ apply_handle_update(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
/*
* Populate updatedCols so that per-column triggers can fire, and so
@@ -1463,7 +1463,7 @@ apply_handle_delete(StringInfo s)
RelationGetDescr(rel->localrel),
&TTSOpsVirtual);
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, rel->localrel, 0);
PushActiveSnapshot(GetTransactionSnapshot());
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 758c3ca097..de542790e4 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -191,7 +191,7 @@ extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- Relation partition_root,
+ Relation rootTargetDesc,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
@@ -551,7 +551,7 @@ exec_rt_fetch(Index rti, EState *estate)
extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
- Index rti);
+ Index rti, Relation rootTargetDesc);
extern int executor_errposition(EState *estate, int location);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d65099c94a..abbe8b1961 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -411,6 +411,9 @@ typedef struct ResultRelInfo
/* relation descriptor for result relation */
Relation ri_RelationDesc;
+ /* relation descriptor of the original target relation */
+ Relation ri_RootTargetDesc;
+
/* # of indices existing on result relation */
int ri_NumIndices;
@@ -493,7 +496,6 @@ typedef struct ResultRelInfo
* ExecInitRoutingInfo, are non-NULL if partition has a different tuple
* format than the root table.
*/
- Relation ri_PartitionRoot;
TupleConversionMap *ri_RootToPartitionMap;
TupleTableSlot *ri_PartitionTupleSlot;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef654..42efca0581 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2458,7 +2458,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 10).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10');
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (20, 1, 10).
+DETAIL: Failing row contains (10, 1, 20).
-- insert with child not null constraint error
INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL);
ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint
@@ -2468,7 +2468,7 @@ ERROR: null value in column "data" of relation "errtst_child_plaindef" violates
DETAIL: Failing row contains (10, 1, null).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL);
ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint
-DETAIL: Failing row contains (20, 1, null).
+DETAIL: Failing row contains (null, 1, 20).
-- insert with shared check constraint error
INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5');
ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small"
@@ -2478,7 +2478,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 5, 5).
INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5');
ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small"
-DETAIL: Failing row contains (20, 5, 5).
+DETAIL: Failing row contains (5, 5, 20).
-- within partition update without child check constraint violation
BEGIN;
UPDATE errtst_parent SET data = data + 1 WHERE partid = 0;
@@ -2523,7 +2523,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 15).
UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10;
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (20, 1, 15).
+DETAIL: Failing row contains (15, 1, 20).
UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20;
ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check"
DETAIL: Failing row contains (0, 1, 15).
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index da50ee3b67..f132d7458b 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -546,7 +546,7 @@ create trigger mlparted11_trig before insert ON mlparted11
-- to the BR trigger mlparted11_trig_fn)
insert into mlparted values (1, 2);
ERROR: new row for relation "mlparted11" violates check constraint "check_b"
-DETAIL: Failing row contains (1, 4).
+DETAIL: Failing row contains (4, 1).
drop trigger mlparted11_trig on mlparted11;
drop function mlparted11_trig_fn();
-- check that inserting into an internal partition successfully results in
@@ -592,12 +592,12 @@ alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 5
alter table mlparted add constraint check_b check (a = 1 and b < 45);
insert into mlparted values (1, 45, 'a');
ERROR: new row for relation "mlparted5a" violates check constraint "check_b"
-DETAIL: Failing row contains (1, 45, a).
+DETAIL: Failing row contains (1, a, 45).
create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b'; return new; end; $$ language plpgsql;
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
-DETAIL: Failing row contains (b, 1, 40).
+DETAIL: Failing row contains (1, b, 40).
drop table mlparted5;
alter table mlparted drop constraint check_b;
-- Check multi-level default partition
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 24905332b1..e77a2ee41c 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2648,7 +2648,7 @@ select tableoid::regclass, * from uv_pt;
create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
insert into uv_ptv_wco values (1, 2);
ERROR: new row violates check option for view "uv_ptv_wco"
-DETAIL: Failing row contains (1, 2, null).
+DETAIL: Failing row contains (2, null, 1).
drop view uv_ptv, uv_ptv_wco;
drop table uv_pt, uv_pt1, uv_pt11;
-- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
@@ -2674,7 +2674,7 @@ with check option;
-- rowtype after tuple-routing
insert into wcowrtest_v2 values (2, 'no such row in sometable');
ERROR: new row violates check option for view "wcowrtest_v2"
-DETAIL: Failing row contains (2, no such row in sometable).
+DETAIL: Failing row contains (no such row in sometable, 2).
drop view wcowrtest_v, wcowrtest_v2;
drop table wcowrtest, sometable;
-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d79f6..8819921d0a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -409,7 +409,7 @@ DETAIL: Failing row contains (a, 4, 120, 1, 1).
-- fail, row movement with check option violation
UPDATE upview set a = 'b', b = 15, c = 120 WHERE b = 4;
ERROR: new row violates check option for view "upview"
-DETAIL: Failing row contains (b, 15, 120, 1, 1).
+DETAIL: Failing row contains (1, 120, 1, b, 15).
-- ok, row movement, check option passes
UPDATE upview set a = 'b', b = 15 WHERE b = 4;
:show_data;
--
2.24.1
v13-0003-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v13-0003-Initialize-result-relation-information-lazily.patchDownload
From a89e994f04d30c54375cb1fb330bcdeb2af2cc8a Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v13 3/3] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also makes changes a few more things to be performed lazily:
* ModifyTableState.mt_partition_tuple_routing in the cross-partition
UPDATE case to the first time ExecCrossPartitionUpdate() is called,
which allows to get rid of the somewhat convoluted logic used to
decide whether ExecInitModifyTable() should initialize it.
* ri_ChildToRootMap is now initialized lazily using a lazy-
initializing-getter for it. There is a regression test output change
in update.out resulting from this change -- whereas previously the
error resulting from partition constraint violation of the target
table (a sub-partitioned partition that is modified directly) would
be shown as occurring on a leaf partition of that table, it is now
shown as occurring on that table itself.
* Delay the opening of result relation indices, ExecOpenIndices(),
to the first time ExecInsert() or ExecUpdate() is called.
---
doc/src/sgml/fdwhandler.sgml | 11 +-
src/backend/commands/explain.c | 12 +-
src/backend/commands/trigger.c | 2 +-
src/backend/executor/execMain.c | 6 +
src/backend/executor/execPartition.c | 106 +--
src/backend/executor/execUtils.c | 21 +
src/backend/executor/nodeModifyTable.c | 877 +++++++++++++------------
src/include/executor/executor.h | 5 +
src/include/nodes/execnodes.h | 1 +
src/test/regress/expected/update.out | 12 +-
10 files changed, 563 insertions(+), 490 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 14d2d923de..1bd49dd2a8 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -520,12 +520,13 @@ BeginForeignModify(ModifyTableState *mtstate,
int eflags);
</programlisting>
- Begin executing a foreign table modification operation. This routine is
- called during executor startup. It should perform any initialization
- needed prior to the actual table modifications. Subsequently,
- <function>ExecForeignInsert/ExecForeignBatchInsert</function>,
+ Begin executing a foreign table modification operation. This is called
+ right before executing the subplan to fetch the tuples to be modified.
+ It should perform any initialization needed prior to the actual table
+ modifications. Subsequently, <function>ExecForeignInsert/
+ ExecForeignBatchInsert</function>,
<function>ExecForeignUpdate</function> or
- <function>ExecForeignDelete</function> will be called for tuple(s) to be
+ <function>ExecForeignDelete</function> will be called for each tuple to be
inserted, updated, or deleted.
</para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 5d7eb3574c..8acccca258 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -3670,6 +3671,8 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
int j;
List *idxNames = NIL;
ListCell *lst;
+ ResultRelInfo *firstResultRel;
+ Relation rootTargetDesc;
switch (node->operation)
{
@@ -3691,17 +3694,22 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
break;
}
+ Assert(mtstate->rootResultRelInfo != NULL);
+ rootTargetDesc = mtstate->rootResultRelInfo->ri_RelationDesc;
+ firstResultRel = ExecGetResultRelation(mtstate, 0, rootTargetDesc);
+
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+ firstResultRel->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j,
+ rootTargetDesc);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 3e7086c5e5..56d8efad34 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -5457,7 +5457,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = relinfo->ri_ChildToRootMap;
+ TupleConversionMap *map = ExecGetChildToRootMap(relinfo);
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 8eb05530e5..f1baa010d8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1246,6 +1246,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1439,6 +1440,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 088b0760d5..cd9c0f9a92 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -507,6 +495,33 @@ ExecFindPartition(ModifyTableState *mtstate,
return rri;
}
+/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ result = ExecGetResultRelation(mtstate, elem->index,
+ proute->partition_root);
+
+ return result;
+}
+
/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
@@ -517,9 +532,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(SubplanResultRelHashElem);
@@ -529,19 +548,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -562,7 +588,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = rootResultRelInfo->ri_RelationDesc,
partrel;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -598,19 +625,27 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo =
+ ExecGetResultRelation(mtstate, 0, proute->partition_root);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
List *wcoExprs = NIL;
ListCell *ll;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -674,7 +709,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot;
ExprContext *econtext;
List *returningList;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
@@ -733,7 +767,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
{
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc;
@@ -903,15 +936,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
}
- /*
- * Also, if transition capture is required, store a map to convert tuples
- * from partition's rowtype to the root partition table's.
- */
- if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
- leaf_part_rri->ri_ChildToRootMap =
- convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
-
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 7f248aa6f3..e0ef32d03c 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1223,3 +1223,24 @@ ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo)
return relInfo->ri_ReturningSlot;
}
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+TupleConversionMap *
+ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid)
+ {
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ Relation targetRel = resultRelInfo->ri_RootTargetDesc;
+
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(relation),
+ RelationGetDescr(targetRel));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 3b218b57b7..c9fc266130 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -186,6 +186,326 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
return ExecProject(projectReturning);
}
+/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+ Relation rootTargetDesc)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ /* Nope, so initialize. */
+ if (resultRelInfo == NULL)
+ {
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ PlanState *subplanstate = mtstate->mt_plans[whichrel];
+ Plan *subplan = subplanstate->plan;
+ bool junk_filter_needed = false;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti, rootTargetDesc);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /*
+ * Determine if the FDW supports batch insert and determine the batch
+ * size (a FDW may support batching, but it may be disabled for the
+ * server/table).
+ *
+ * We only do this for INSERT, so that for UPDATE/DELETE the batch
+ * size remains set to 0.
+ */
+ if (operation == CMD_INSERT)
+ {
+ 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;
+
+ Assert(resultRelInfo->ri_BatchSize >= 1);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize JunkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the
+ * tlist. UPDATE and DELETE always need a filter, since there's always
+ * at least one junk attribute present --- no need to look first.
+ * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+ * case of a foreign data wrapper it might be a set of junk attributes
+ * sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an
+ * INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * While at it, also initialize a result relation specific slot that
+ * will be used to copy the plan's output tuples into.
+ */
+ Assert(mtstate->mt_scans[whichrel] == NULL);
+ mtstate->mt_scans[whichrel] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
/*
* ExecCheckTupleVisible -- verify tuple is visible
*
@@ -412,6 +732,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -1201,7 +1524,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;
@@ -1220,13 +1542,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, mtstate, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1280,7 +1616,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,
@@ -1345,6 +1681,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1458,6 +1797,13 @@ lreplace:;
*retry_slot;
bool retry;
+ /*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
/*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
@@ -2048,11 +2394,12 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -2062,6 +2409,7 @@ ExecModifyTable(PlanState *pstate)
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
List *relinfos = NIL;
ListCell *lc;
+ Relation rootTargetDesc = node->rootResultRelInfo->ri_RelationDesc;
CHECK_FOR_INTERRUPTS();
@@ -2096,9 +2444,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -2122,17 +2468,27 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL &&
+ bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans))
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+ rootTargetDesc);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2141,6 +2497,17 @@ ExecModifyTable(PlanState *pstate)
break;
}
+ /*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+ rootTargetDesc);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
/*
* Ensure input tuple is the right format for the target relation.
*/
@@ -2316,13 +2683,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2339,11 +2703,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2353,12 +2758,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* - the root partitioned table used for tuple routing.
*
* If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * elsewhere in the plan unless if it's an INSERT and its RT index is
+ * given explicitly in node->rootRelation. Otherwise (i.e. table
+ * inheritance) the target relation is the first relation in the
+ * node->resultRelations list.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
rel = ExecGetRangeTableRelation(estate, node->rootRelation);
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
@@ -2369,14 +2775,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
Index rootRelation = linitial_int(node->resultRelations);
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
rel = ExecGetRangeTableRelation(estate, rootRelation);
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
- rel);
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0, rel);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2390,261 +2800,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
- /*
- * 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 an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
-
/*
* If we have any secondary relations in an UPDATE or DELETE, they need to
* be treated like non-locked relations in SELECT FOR UPDATE, ie, the
@@ -2680,149 +2844,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
mtstate->mt_arowmarks[0]);
- /*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
- * Determine if the FDW supports batch insert and determine the batch
- * size (a FDW may support batching, but it may be disabled for the
- * server/table).
- *
- * We only do this for INSERT, so that for UPDATE/DELETE the batch
- * size remains set to 0.
- */
- if (operation == CMD_INSERT)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- 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;
-
- Assert(resultRelInfo->ri_BatchSize >= 1);
-
- resultRelInfo++;
- }
- }
-
/*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
@@ -2852,20 +2873,6 @@ ExecEndModifyTable(ModifyTableState *node)
{
int i;
- /*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
/*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index de542790e4..93be26e210 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -573,6 +573,7 @@ extern int ExecCleanTargetListLength(List *targetlist);
extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
+extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
/*
* prototypes from functions in execIndexing.c
@@ -617,4 +618,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+ Relation rootTargetDesc);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index abbe8b1961..3f5f309ec1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -505,6 +505,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copyfrom.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 8819921d0a..51f42abf2a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
2.24.1
On Mon, Jan 25, 2021 at 2:23 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Tue, Dec 22, 2020 at 5:16 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Mon, Dec 7, 2020 at 3:53 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Nov 12, 2020 at 5:04 PM Amit Langote <amitlangote09@gmail.com> wrote:
Attached new 0002 which does these adjustments. I went with
ri_RootTargetDesc to go along with ri_RelationDesc.Also, I have updated the original 0002 (now 0003) to make
GetChildToRootMap() use ri_RootTargetDesc instead of
ModifyTableState.rootResultRelInfo.ri_RelationDesc, so that even
AfterTriggerSaveEvent() can now use that function. This allows us to
avoid having to initialize ri_ChildToRootMap anywhere but inside
GetChildRootMap(), with that long comment defending doing so. :-)These needed to be rebased due to recent copy.c upheavals. Attached.
Needed to be rebased again.
And again, this time over the recent batch insert API related patches.
Another rebase.
I've dropped what was patch 0001 in the previous set, because I think
it has been rendered unnecessary due to recently committed changes.
However, the rebase led to a couple of additional regression test
output changes that I think are harmless. The changes are caused by
the fact that ri_RootResultRelInfo now gets initialized in *all* child
result relations, not just those that participate in tuple routing.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v14-0001-Set-ForeignScanState.resultRelInfo-lazily.patchapplication/octet-stream; name=v14-0001-Set-ForeignScanState.resultRelInfo-lazily.patchDownload
From 0b81e7a1b765160f5b283c1f5f1b8cdf52588ea9 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v14 1/2] Set ForeignScanState.resultRelInfo lazily
Instead of doing it in ExecInitForeignScan(), do it on the first
ForeignNext() call. This also moves the BeginDirectModify() call
into ForeignNext().
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos lazily when it actually begins executing,
instead of in ExecInitModifyTable().
---
doc/src/sgml/fdwhandler.sgml | 7 +++--
src/backend/executor/nodeForeignscan.c | 39 ++++++++++++++++++--------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 854913ae5f..14d2d923de 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -967,9 +967,10 @@ BeginDirectModify(ForeignScanState *node,
</programlisting>
Prepare to execute a direct modification on the remote server.
- This is called during executor startup. It should perform any
- initialization needed prior to the direct modification (that should be
- done upon the first call to <function>IterateDirectModify</function>).
+ This is called right before the first time <function>IterateDirectModify</function>
+ is called on the node. It should perform any initialization needed prior to the
+ direct modification (that should be done upon the first call to
+ <function>IterateDirectModify</function>).
The <structname>ForeignScanState</structname> node has already been created, but
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0969e53c3a..25f6fd0e49 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -49,7 +49,31 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
+ {
+ /*
+ * For FDW's convenience, look up the result relation info and set
+ * ForeignScanState.resultRelInfo if not already done. This is also
+ * a good time to call BeginDirectModify().
+ */
+ Assert(plan->resultRelation > 0);
+ if (node->resultRelInfo == NULL)
+ {
+ EState *estate = node->ss.ps.state;
+ ResultRelInfo *rInfo = estate->es_result_relations[plan->resultRelation - 1];
+
+ /* ExecInitModifyTable() must have initialized one already. */
+ Assert(rInfo != NULL);
+ node->resultRelInfo = rInfo;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ Assert(rInfo->ri_FdwRoutine != NULL &&
+ rInfo->ri_FdwRoutine->BeginDirectModify != NULL);
+ rInfo->ri_FdwRoutine->BeginDirectModify(node,
+ estate->es_top_eflags);
+ MemoryContextSwitchTo(oldcontext);
+ }
slot = node->fdwroutine->IterateDirectModify(node);
+ }
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@@ -215,24 +239,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, any
+ * additional initializations are performed right before calling
+ * IterateDirectModify() for the first time.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
--
2.24.1
v14-0002-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v14-0002-Initialize-result-relation-information-lazily.patchDownload
From 4f38d1c729cca5fbe438ab9ba03d1fbb0c823400 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v14 2/2] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also makes changes a few more things to be performed lazily:
* ModifyTableState.mt_partition_tuple_routing in the cross-partition
UPDATE case to the first time ExecCrossPartitionUpdate() is called,
which allows to get rid of the somewhat convoluted logic used to
decide whether ExecInitModifyTable() should initialize it.
* ri_ChildToRootMap is now initialized lazily using a lazy-
initializing-getter for it. There is a regression test output change
in update.out resulting from this change -- whereas previously the
error resulting from partition constraint violation of the target
table (a sub-partitioned partition that is modified directly) would
be shown as occurring on a leaf partition of that table, it is now
shown as occurring on that table itself.
* Delay the opening of result relation indices, ExecOpenIndices(),
to the first time ExecInsert() or ExecUpdate() is called.
In addition to those changes, this also changes things around
ri_RootResultRelInfo to set it for *all* child result relations,
instead of only those that are tuple routing target relations.
That leads to a couple of harmless regression test output changes;
see diffs for inherit.out and privilege.out.
---
doc/src/sgml/fdwhandler.sgml | 11 +-
src/backend/commands/copyfrom.c | 2 +-
src/backend/commands/explain.c | 9 +-
src/backend/commands/trigger.c | 2 +-
src/backend/executor/execMain.c | 10 +
src/backend/executor/execPartition.c | 111 +--
src/backend/executor/execUtils.c | 23 +
src/backend/executor/nodeModifyTable.c | 886 ++++++++++++-----------
src/backend/replication/logical/worker.c | 2 +-
src/include/executor/execPartition.h | 1 -
src/include/executor/executor.h | 4 +
src/include/nodes/execnodes.h | 1 +
src/test/regress/expected/inherit.out | 2 +-
src/test/regress/expected/privileges.out | 2 +-
src/test/regress/expected/update.out | 12 +-
15 files changed, 573 insertions(+), 505 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 14d2d923de..1bd49dd2a8 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -520,12 +520,13 @@ BeginForeignModify(ModifyTableState *mtstate,
int eflags);
</programlisting>
- Begin executing a foreign table modification operation. This routine is
- called during executor startup. It should perform any initialization
- needed prior to the actual table modifications. Subsequently,
- <function>ExecForeignInsert/ExecForeignBatchInsert</function>,
+ Begin executing a foreign table modification operation. This is called
+ right before executing the subplan to fetch the tuples to be modified.
+ It should perform any initialization needed prior to the actual table
+ modifications. Subsequently, <function>ExecForeignInsert/
+ ExecForeignBatchInsert</function>,
<function>ExecForeignUpdate</function> or
- <function>ExecForeignDelete</function> will be called for tuple(s) to be
+ <function>ExecForeignDelete</function> will be called for each tuple to be
inserted, updated, or deleted.
</para>
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 796ca7b3f7..362b378b57 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -694,7 +694,7 @@ CopyFrom(CopyFromState cstate)
* CopyFrom tuple routing.
*/
if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
- proute = ExecSetupPartitionTupleRouting(estate, NULL, cstate->rel);
+ proute = ExecSetupPartitionTupleRouting(estate, cstate->rel);
if (cstate->whereClause)
cstate->qualexpr = ExecInitQual(castNode(List, cstate->whereClause),
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index f80e379973..82b826c2d6 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -3670,6 +3671,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
int j;
List *idxNames = NIL;
ListCell *lst;
+ ResultRelInfo *firstResultRel;
switch (node->operation)
{
@@ -3691,17 +3693,20 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
break;
}
+ Assert(mtstate->rootResultRelInfo != NULL);
+ firstResultRel = ExecGetResultRelation(mtstate, 0);
+
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
- mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation));
+ firstResultRel->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 8908847c6c..f51229c030 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -5445,7 +5445,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = relinfo->ri_ChildToRootMap;
+ TupleConversionMap *map = ExecGetChildToRootMap(relinfo);
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c74ce36ffb..a3f9cea408 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1228,11 +1228,16 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ReturningSlot = NULL;
resultRelInfo->ri_TrigOldSlot = NULL;
resultRelInfo->ri_TrigNewSlot = NULL;
+ /*
+ * For other child relations that are not tuple routing target relations,
+ * this is set in ExecGetResultRelation().
+ */
resultRelInfo->ri_RootResultRelInfo = partition_root_rri;
resultRelInfo->ri_RootToPartitionMap = NULL; /* set by
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1426,6 +1431,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index b9e4f2d80b..8899d408f9 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -157,10 +157,11 @@ typedef struct PartitionDispatchData
typedef struct SubplanResultRelHashElem
{
Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
+ int index;
} SubplanResultRelHashElem;
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -215,11 +216,9 @@ static void find_matching_subplans_recurse(PartitionPruningData *prunedata,
* it should be estate->es_query_cxt.
*/
PartitionTupleRouting *
-ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
- Relation rel)
+ExecSetupPartitionTupleRouting(EState *estate, Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -241,17 +240,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0, NULL);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -351,7 +339,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -368,20 +355,19 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -509,6 +495,32 @@ ExecFindPartition(ModifyTableState *mtstate,
return rri;
}
+/*
+ * ExecLookupUpdateResultRelByOid
+ * If the table with given OID appears in the list of result relations
+ * to be updated by the given ModifyTable node, return its
+ * ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+ PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+ SubplanResultRelHashElem *elem;
+ ResultRelInfo *result = NULL;
+
+ Assert(proute != NULL);
+ if (proute->subplan_resultrel_htab == NULL)
+ ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+ elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+ HASH_FIND, NULL);
+
+ if (elem)
+ result = ExecGetResultRelation(mtstate, elem->index);
+
+ return result;
+}
+
/*
* ExecHashSubPlanResultRelsByOid
* Build a hash table to allow fast lookups of subplan ResultRelInfos by
@@ -519,9 +531,13 @@ static void
ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ ListCell *l;
HASHCTL ctl;
HTAB *htab;
int i;
+ MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(SubplanResultRelHashElem);
@@ -531,26 +547,27 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ /*
+ * Map each result relation's OID to its ordinal position in
+ * plan->resultRelations.
+ */
+ i = 0;
+ foreach(l, plan->resultRelations)
{
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+ Index rti = lfirst_int(l);
+ RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+ Oid partoid = rte->relid;
bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
SubplanResultRelHashElem *elem;
elem = (SubplanResultRelHashElem *)
hash_search(htab, &partoid, HASH_ENTER, &found);
Assert(!found);
- elem->rri = rri;
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
+ elem->index = i++;
}
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -570,8 +587,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
{
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation partrel;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -607,12 +624,21 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo =
+ ExecGetResultRelation(mtstate, 0);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
@@ -909,15 +935,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
}
- /*
- * Also, if transition capture is required, store a map to convert tuples
- * from partition's rowtype to the root partition table's.
- */
- if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
- leaf_part_rri->ri_ChildToRootMap =
- convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(rootResultRelInfo->ri_RelationDesc));
-
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index c734283bfe..02f351e5ab 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1309,3 +1309,26 @@ ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate)
return bms_union(ExecGetUpdatedCols(relinfo, estate),
ExecGetExtraUpdatedCols(relinfo, estate));
}
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * query's main target ("root") relation's format, possibly initializing it
+ * if not already done.
+ */
+TupleConversionMap *
+ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid &&
+ resultRelInfo->ri_RootResultRelInfo)
+ {
+ ResultRelInfo *targetRelInfo;
+
+ targetRelInfo = resultRelInfo->ri_RootResultRelInfo;
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ RelationGetDescr(targetRelInfo->ri_RelationDesc));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2993ba43e3..bfa990219f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -186,6 +186,332 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
return ExecProject(projectReturning);
}
+/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ if (resultRelInfo == NULL)
+ {
+ /* Nope, so initialize. */
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ PlanState *subplanstate = mtstate->mt_plans[whichrel];
+ Plan *subplan = subplanstate->plan;
+ bool junk_filter_needed = false;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /*
+ * For child result relations, store the root result relation pointer
+ * to be used in places where the mtstate is not available.
+ */
+ if (resultRelInfo != mtstate->rootResultRelInfo)
+ resultRelInfo->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /*
+ * Determine if the FDW supports batch insert and determine the batch
+ * size (a FDW may support batching, but it may be disabled for the
+ * server/table).
+ *
+ * We only do this for INSERT, so that for UPDATE/DELETE the batch
+ * size remains set to 0.
+ */
+ if (operation == CMD_INSERT)
+ {
+ 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;
+
+ Assert(resultRelInfo->ri_BatchSize >= 1);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nplans == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize JunkFilter if needed.
+ *
+ * INSERT queries need a filter if there are any junk attrs in the
+ * tlist. UPDATE and DELETE always need a filter, since there's always
+ * at least one junk attribute present --- no need to look first.
+ * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+ * case of a foreign data wrapper it might be a set of junk attributes
+ * sufficient to identify the remote row.
+ *
+ * If there are multiple result relations, each one needs its own junk
+ * filter. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a filter and some not.
+ *
+ * This is also a convenient place to verify that the output of an
+ * INSERT or UPDATE matches the target table(s).
+ */
+ switch (operation)
+ {
+ case CMD_INSERT:
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+ break;
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ junk_filter_needed = true;
+ break;
+ default:
+ elog(ERROR, "unknown operation");
+ break;
+ }
+
+ if (junk_filter_needed)
+ {
+ JunkFilter *j;
+ TupleTableSlot *junkresslot;
+
+ junkresslot =
+ ExecInitExtraTupleSlot(estate, NULL,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /*
+ * For an INSERT or UPDATE, the result tuple must always match
+ * the target table's descriptor. For a DELETE, it won't
+ * (indeed, there's probably no non-junk output columns).
+ */
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+ j = ExecInitJunkFilterInsertion(subplan->targetlist,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ junkresslot);
+ }
+ else
+ j = ExecInitJunkFilter(subplan->targetlist,
+ junkresslot);
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be
+ * a wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ resultRelInfo->ri_junkFilter = j;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ subplan->targetlist);
+
+ /*
+ * While at it, also initialize a result relation specific slot that
+ * will be used to copy the plan's output tuples into.
+ */
+ Assert(mtstate->mt_scans[whichrel] == NULL);
+ mtstate->mt_scans[whichrel] =
+ ExecInitExtraTupleSlot(mtstate->ps.state,
+ ExecGetResultType(subplanstate),
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
/*
* ExecCheckTupleVisible -- verify tuple is visible
*
@@ -412,6 +738,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -565,7 +894,7 @@ ExecInsert(ModifyTableState *mtstate,
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
- (resultRelInfo->ri_RootResultRelInfo == NULL ||
+ (resultRelInfo->ri_RangeTableIndex != 0 ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);
@@ -1200,7 +1529,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;
@@ -1219,13 +1547,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1279,7 +1621,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,
@@ -1344,6 +1686,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1457,6 +1802,13 @@ lreplace:;
*retry_slot;
bool retry;
+ /*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
/*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
@@ -2047,11 +2399,12 @@ static TupleTableSlot *
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ ModifyTable *plan = (ModifyTable *) node->ps.plan;
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
- JunkFilter *junkfilter;
+ JunkFilter *junkfilter = NULL;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid;
@@ -2095,9 +2448,7 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -2121,17 +2472,26 @@ ExecModifyTable(PlanState *pstate)
if (pstate->ps_ExprContext)
ResetExprContext(pstate->ps_ExprContext);
+ /*
+ * FDWs that can push down a modify operation would need to see the
+ * ResultRelInfo, so fetch one if not already done before executing
+ * the subplan, potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL &&
+ bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans))
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
{
- /* advance to next subplan if any */
+ /* Signal to initialize the next plan's relation. */
+ resultRelInfo = NULL;
+
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
@@ -2140,6 +2500,16 @@ ExecModifyTable(PlanState *pstate)
break;
}
+ /*
+ * Fetch the result relation for the current plan if not already done,
+ * potentially opening it for the first time.
+ */
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ }
+
/*
* Ensure input tuple is the right format for the target relation.
*/
@@ -2315,13 +2685,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
- ResultRelInfo *resultRelInfo;
Plan *subplan;
- ListCell *l,
- *l1;
+ 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)));
@@ -2338,11 +2705,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mt_plans".
+ */
+ mtstate->mt_nplans = nplans;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ i = 0;
+ foreach(l, node->plans)
+ {
+ subplan = (Plan *) lfirst(l);
+
+ mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+ }
+
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2352,12 +2760,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* - the root partitioned table used for tuple routing.
*
* If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * elsewhere in the plan unless if it's an INSERT and its RT index is
+ * given explicitly in node->rootRelation. Otherwise (i.e. table
+ * inheritance) the target relation is the first relation in the
+ * node->resultRelations list.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
@@ -2365,13 +2774,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
else
{
- mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ /*
+ * Unlike a partitioned target relation, the target relation in this
+ * case will be actually used by ExecModifyTable(), so use
+ * ExecGetResultRelation() to get the ResultRelInfo, because it
+ * initializes some fields that a bare InitResultRelInfo() doesn't.
+ */
+ mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0);
+ Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2385,263 +2798,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
+ * Build state for tuple routing if it's an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see ExecCrossPartitionUpdate().
*/
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
- {
- Index resultRelation = lfirst_int(l);
-
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- /*
- * 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;
-
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* Also let FDWs init themselves for foreign-table result rels */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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));
- resultRelInfo++;
- i++;
- }
-
- /* Get the 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.
- */
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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- TupleDesc relationDesc;
- TupleDesc tupDesc;
-
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
+ ExecSetupPartitionTupleRouting(estate, rel);
/*
* If we have any secondary relations in an UPDATE or DELETE, they need to
@@ -2678,149 +2843,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
mtstate->mt_arowmarks[0]);
- /*
- * Initialize the junk filter(s) if needed. INSERT queries need a filter
- * if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
- *
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- {
- bool junk_filter_needed = false;
-
- switch (operation)
- {
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
-
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- JunkFilter *j;
- TupleTableSlot *junkresslot;
-
- subplan = mtstate->mt_plans[i]->plan;
-
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
-
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
-
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
- }
- else
- {
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- }
- }
-
- /*
- * Determine if the FDW supports batch insert and determine the batch
- * size (a FDW may support batching, but it may be disabled for the
- * server/table).
- *
- * We only do this for INSERT, so that for UPDATE/DELETE the batch
- * size remains set to 0.
- */
- if (operation == CMD_INSERT)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
- {
- 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;
-
- Assert(resultRelInfo->ri_BatchSize >= 1);
-
- resultRelInfo++;
- }
- }
-
/*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
@@ -2850,20 +2872,6 @@ ExecEndModifyTable(ModifyTableState *node)
{
int i;
- /*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nplans; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
/*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index eb7db89cef..203cec91be 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1591,7 +1591,7 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo,
mtstate->ps.state = estate;
mtstate->operation = operation;
mtstate->resultRelInfo = relinfo;
- proute = ExecSetupPartitionTupleRouting(estate, mtstate, parentrel);
+ proute = ExecSetupPartitionTupleRouting(estate, parentrel);
/*
* Find the partition to which the "search tuple" belongs.
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index d30ffde7d9..694e38b7dd 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -111,7 +111,6 @@ typedef struct PartitionPruneState
} PartitionPruneState;
extern PartitionTupleRouting *ExecSetupPartitionTupleRouting(EState *estate,
- ModifyTableState *mtstate,
Relation rel);
extern ResultRelInfo *ExecFindPartition(ModifyTableState *mtstate,
ResultRelInfo *rootResultRelInfo,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 071e363d54..40d80221da 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -573,6 +573,7 @@ extern int ExecCleanTargetListLength(List *targetlist);
extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
+extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
@@ -622,4 +623,7 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b6a88ff76b..cf5cc637d1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -507,6 +507,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copyfrom.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef654..fbfff7b5aa 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2494,7 +2494,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 15).
UPDATE errtst_parent SET data = data + 10 WHERE partid = 20;
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (15, 1, 20).
+DETAIL: Failing row contains (20, 1, 15).
-- direct leaf partition update, without partition id violation
BEGIN;
UPDATE errtst_child_fastdef SET partid = 1 WHERE partid = 0;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 46a69fc0dc..5b05afcc51 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -685,7 +685,7 @@ DETAIL: Failing row contains (a, b, c) = (aaa, null, null).
-- simple update.
UPDATE errtst SET b = NULL;
ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint
-DETAIL: Failing row contains (b) = (null).
+DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc).
-- partitioning key is updated, doesn't move the row.
UPDATE errtst SET a = 'aaa', b = NULL;
ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d79f6..0ad0d1a4df 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
2.24.1
Amit Langote <amitlangote09@gmail.com> writes:
[ v14-0002-Initialize-result-relation-information-lazily.patch ]
Needs YA rebase over 86dc90056.
regards, tom lane
On Thu, Apr 1, 2021 at 3:12 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Langote <amitlangote09@gmail.com> writes:
[ v14-0002-Initialize-result-relation-information-lazily.patch ]
Needs YA rebase over 86dc90056.
Done. I will post the updated results for -Mprepared benchmarks I did
in the other thread shortly.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v15-0001-Set-ForeignScanState.resultRelInfo-lazily.patchapplication/octet-stream; name=v15-0001-Set-ForeignScanState.resultRelInfo-lazily.patchDownload
From 83c58c2cabad0ee2d591dc55256050214dcca0fe Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Mon, 19 Oct 2020 17:17:33 +0900
Subject: [PATCH v15 1/2] Set ForeignScanState.resultRelInfo lazily
Instead of doing it in ExecInitForeignScan(), do it on the first
ForeignNext() call. This also moves the BeginDirectModify() call
into ForeignNext().
This is in preparation of a later commit to make ModifyTable node
initialize ResultRelInfos after performing ExecInitNode() on the
subplan.
---
doc/src/sgml/fdwhandler.sgml | 7 +++--
src/backend/executor/nodeForeignscan.c | 39 ++++++++++++++++++--------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 0f2397df49..5e3ec5c0e9 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -975,9 +975,10 @@ BeginDirectModify(ForeignScanState *node,
</programlisting>
Prepare to execute a direct modification on the remote server.
- This is called during executor startup. It should perform any
- initialization needed prior to the direct modification (that should be
- done upon the first call to <function>IterateDirectModify</function>).
+ This is called right before the first time <function>IterateDirectModify</function>
+ is called on the node. It should perform any initialization needed prior to the
+ direct modification (that should be done upon the first call to
+ <function>IterateDirectModify</function>).
The <structname>ForeignScanState</structname> node has already been created, but
its <structfield>fdw_state</structfield> field is still NULL. Information about
the table to modify is accessible through the
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 898890fb08..e0f12080c4 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -49,7 +49,31 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
+ {
+ /*
+ * For FDW's convenience, look up the result relation info and set
+ * ForeignScanState.resultRelInfo if not already done. This is also
+ * a good time to call BeginDirectModify().
+ */
+ Assert(plan->resultRelation > 0);
+ if (node->resultRelInfo == NULL)
+ {
+ EState *estate = node->ss.ps.state;
+ ResultRelInfo *rInfo = estate->es_result_relations[plan->resultRelation - 1];
+
+ /* ExecInitModifyTable() must have initialized one already. */
+ Assert(rInfo != NULL);
+ node->resultRelInfo = rInfo;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ Assert(rInfo->ri_FdwRoutine != NULL &&
+ rInfo->ri_FdwRoutine->BeginDirectModify != NULL);
+ rInfo->ri_FdwRoutine->BeginDirectModify(node,
+ estate->es_top_eflags);
+ MemoryContextSwitchTo(oldcontext);
+ }
slot = node->fdwroutine->IterateDirectModify(node);
+ }
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@@ -215,24 +239,17 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
- /*
- * For the FDW's convenience, look up the modification target relation's.
- * ResultRelInfo.
- */
- if (node->resultRelation > 0)
- scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
-
/* Initialize any outer plan. */
if (outerPlan(node))
outerPlanState(scanstate) =
ExecInitNode(outerPlan(node), estate, eflags);
/*
- * Tell the FDW to initialize the scan.
+ * Tell the FDW to initialize the scan. For modify operations, any
+ * additional initializations are performed right before calling
+ * IterateDirectModify() for the first time.
*/
- if (node->operation != CMD_SELECT)
- fdwroutine->BeginDirectModify(scanstate, eflags);
- else
+ if (node->operation == CMD_SELECT)
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
--
2.24.1
v15-0002-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v15-0002-Initialize-result-relation-information-lazily.patchDownload
From f15febf30d6c4b1544fe7b075c55deea4a3b542d Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v15 2/2] Initialize result relation information lazily
Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many child result
relations appearing in that array may actually have any rows to
update or delete.
This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.
This also makes changes a few more things to be performed lazily:
* ModifyTableState.mt_partition_tuple_routing in the cross-partition
UPDATE case to the first time ExecCrossPartitionUpdate() is called,
which allows to get rid of the somewhat convoluted logic used to
decide whether ExecInitModifyTable() should initialize it.
* ri_ChildToRootMap is now initialized lazily using a lazy-
initializing-getter for it. There is a regression test output change
in update.out resulting from this change -- whereas previously the
error resulting from partition constraint violation of the target
table (a sub-partitioned partition that is modified directly) would
be shown as occurring on a leaf partition of that table, it is now
shown as occurring on that table itself.
* Delay the opening of result relation indices, ExecOpenIndices(),
to the first time ExecInsert() or ExecUpdate() is called.
Couple of other changes:
* ri_RootResultRelInfo is now set for *all* child result relations,
instead of only those that are tuple routing target relations. This
allow code that doesn't get passed ModifyTableState to have access
to the command's "root" result relation. That leads to a couple of
harmless regression test output changes; see diffs for inherit.out
and privilege.out. To distinguish tuple routing target child
relations from those owned by a ModifyTable node, the code now must
check ri_RangeTableIndex which is only valid (> 0) in the latter.
* PartitionTupleRouting.subplan_resultrel_htab is removed in favor
of using ModifyTableState.mt_resultOidHash to look up an UPDATE
result relation by OID.
---
doc/src/sgml/fdwhandler.sgml | 11 +-
src/backend/commands/copyfrom.c | 2 +-
src/backend/commands/explain.c | 9 +-
src/backend/commands/trigger.c | 2 +-
src/backend/executor/execMain.c | 11 +
src/backend/executor/execPartition.c | 159 +---
src/backend/executor/execUtils.c | 23 +
src/backend/executor/nodeModifyTable.c | 1030 +++++++++++-----------
src/backend/replication/logical/worker.c | 2 +-
src/include/executor/execPartition.h | 1 -
src/include/executor/executor.h | 6 +-
src/include/nodes/execnodes.h | 7 +-
src/test/regress/expected/inherit.out | 2 +-
src/test/regress/expected/privileges.out | 2 +-
src/test/regress/expected/update.out | 12 +-
15 files changed, 645 insertions(+), 634 deletions(-)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 5e3ec5c0e9..3d20e5b7e0 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -525,12 +525,13 @@ BeginForeignModify(ModifyTableState *mtstate,
int eflags);
</programlisting>
- Begin executing a foreign table modification operation. This routine is
- called during executor startup. It should perform any initialization
- needed prior to the actual table modifications. Subsequently,
- <function>ExecForeignInsert/ExecForeignBatchInsert</function>,
+ Begin executing a foreign table modification operation. This is called
+ right before executing the subplan to fetch the tuples to be modified.
+ It should perform any initialization needed prior to the actual table
+ modifications. Subsequently, <function>ExecForeignInsert/
+ ExecForeignBatchInsert</function>,
<function>ExecForeignUpdate</function> or
- <function>ExecForeignDelete</function> will be called for tuple(s) to be
+ <function>ExecForeignDelete</function> will be called for each tuple to be
inserted, updated, or deleted.
</para>
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 74dbb709fe..f430042da3 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -696,7 +696,7 @@ CopyFrom(CopyFromState cstate)
* CopyFrom tuple routing.
*/
if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
- proute = ExecSetupPartitionTupleRouting(estate, NULL, cstate->rel);
+ proute = ExecSetupPartitionTupleRouting(estate, cstate->rel);
if (cstate->whereClause)
cstate->qualexpr = ExecInitQual(castNode(List, cstate->whereClause),
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 872aaa7aed..1aac176d96 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
+#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
@@ -3690,6 +3691,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
int j;
List *idxNames = NIL;
ListCell *lst;
+ ResultRelInfo *firstResultRel;
switch (node->operation)
{
@@ -3711,17 +3713,20 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
break;
}
+ Assert(mtstate->rootResultRelInfo != NULL);
+ firstResultRel = ExecGetResultRelation(mtstate, 0);
+
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nrels > 1 ||
(mtstate->mt_nrels == 1 &&
- mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation));
+ firstResultRel->ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nrels; j++)
{
- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+ ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j);
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
if (labeltargets)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index a5ceb1698c..3421014e47 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -5479,7 +5479,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = relinfo->ri_ChildToRootMap;
+ TupleConversionMap *map = ExecGetChildToRootMap(relinfo);
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 163242f54e..3437600314 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1231,11 +1231,17 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ReturningSlot = NULL;
resultRelInfo->ri_TrigOldSlot = NULL;
resultRelInfo->ri_TrigNewSlot = NULL;
+ /*
+ * Only ExecInitPartitionInfo() passes partition_root_rri. For child
+ * relations that are not tuple routing target relations, this is set in
+ * ExecGetResultRelation().
+ */
resultRelInfo->ri_RootResultRelInfo = partition_root_rri;
resultRelInfo->ri_RootToPartitionMap = NULL; /* set by
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1429,6 +1435,11 @@ ExecCloseResultRelations(EState *estate)
ResultRelInfo *resultRelInfo = lfirst(l);
ExecCloseIndices(resultRelInfo);
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+ resultRelInfo);
}
/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 558060e080..a3a65e488f 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -68,9 +68,15 @@
* Array of 'max_partitions' elements containing a pointer to a
* ResultRelInfo for every leaf partitions touched by tuple routing.
* Some of these are pointers to ResultRelInfos which are borrowed out of
- * 'subplan_resultrel_htab'. The remainder have been built especially
- * for tuple routing. See comment for PartitionDispatchData->indexes for
- * details on how this array is indexed.
+ * the owning ModifyTableState node. The remainder have been built
+ * especially for tuple routing. See comment for
+ * PartitionDispatchData->indexes for details on how this array is
+ * indexed.
+ *
+ * is_update_rel
+ * Array of 'max_partitions' booleans recording whether a given entry
+ * in 'partitions' is a ResultRelInfo pointer borrowed from a matching
+ * UPDATE result relation in the owning ModifyTableState node
*
* num_partitions
* The current number of items stored in the 'partitions' array. Also
@@ -80,12 +86,6 @@
* max_partitions
* The current allocated size of the 'partitions' array.
*
- * subplan_resultrel_htab
- * Hash table to store subplan ResultRelInfos by Oid. This is used to
- * cache ResultRelInfos from targets of an UPDATE ModifyTable node;
- * NULL in other cases. Some of these may be useful for tuple routing
- * to save having to build duplicates.
- *
* memcxt
* Memory context used to allocate subsidiary structs.
*-----------------------
@@ -98,9 +98,9 @@ struct PartitionTupleRouting
int num_dispatch;
int max_dispatch;
ResultRelInfo **partitions;
+ bool *is_update_rel;
int num_partitions;
int max_partitions;
- HTAB *subplan_resultrel_htab;
MemoryContext memcxt;
};
@@ -153,16 +153,7 @@ typedef struct PartitionDispatchData
int indexes[FLEXIBLE_ARRAY_MEMBER];
} PartitionDispatchData;
-/* struct to hold result relations coming from UPDATE subplans */
-typedef struct SubplanResultRelHashElem
-{
- Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
-} SubplanResultRelHashElem;
-
-static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
- PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
EState *estate, PartitionTupleRouting *proute,
PartitionDispatch dispatch,
@@ -173,7 +164,7 @@ static void ExecInitRoutingInfo(ModifyTableState *mtstate,
PartitionTupleRouting *proute,
PartitionDispatch dispatch,
ResultRelInfo *partRelInfo,
- int partidx);
+ int partidx, bool is_update_rel);
static PartitionDispatch ExecInitPartitionDispatchInfo(EState *estate,
PartitionTupleRouting *proute,
Oid partoid, PartitionDispatch parent_pd,
@@ -215,11 +206,9 @@ static void find_matching_subplans_recurse(PartitionPruningData *prunedata,
* it should be estate->es_query_cxt.
*/
PartitionTupleRouting *
-ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
- Relation rel)
+ExecSetupPartitionTupleRouting(EState *estate, Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -241,17 +230,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0, NULL);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -351,7 +329,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -368,20 +345,18 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupResultRelByOid(mtstate, partoid, true);
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -391,7 +366,7 @@ ExecFindPartition(ModifyTableState *mtstate,
* subsequent tuples routed to this partition.
*/
ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
- rri, partidx);
+ rri, partidx, true);
}
}
@@ -509,50 +484,6 @@ ExecFindPartition(ModifyTableState *mtstate,
return rri;
}
-/*
- * ExecHashSubPlanResultRelsByOid
- * Build a hash table to allow fast lookups of subplan ResultRelInfos by
- * partition Oid. We also populate the subplan ResultRelInfo with an
- * ri_PartitionRoot.
- */
-static void
-ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
- PartitionTupleRouting *proute)
-{
- HASHCTL ctl;
- HTAB *htab;
- int i;
-
- ctl.keysize = sizeof(Oid);
- ctl.entrysize = sizeof(SubplanResultRelHashElem);
- ctl.hcxt = CurrentMemoryContext;
-
- htab = hash_create("PartitionTupleRouting table", mtstate->mt_nrels,
- &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
- proute->subplan_resultrel_htab = htab;
-
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nrels; i++)
- {
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
- bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
- SubplanResultRelHashElem *elem;
-
- elem = (SubplanResultRelHashElem *)
- hash_search(htab, &partoid, HASH_ENTER, &found);
- Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
- }
-}
-
/*
* ExecInitPartitionInfo
* Lock the partition and initialize ResultRelInfo. Also setup other
@@ -571,8 +502,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Oid partOid = dispatch->partdesc->oids[partidx];
Relation partrel;
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+ Relation firstResultRel = NULL;
+ Index firstVarno = 0;
ResultRelInfo *leaf_part_rri;
MemoryContext oldcxt;
AttrMap *part_attmap = NULL;
@@ -608,12 +539,21 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ if (node)
+ {
+ ResultRelInfo *firstResultRelInfo =
+ ExecGetResultRelation(mtstate, 0);
+
+ firstResultRel = firstResultRelInfo->ri_RelationDesc;
+ firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+ }
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
@@ -734,7 +674,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/* Set up information needed for routing tuples to the partition. */
ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
- leaf_part_rri, partidx);
+ leaf_part_rri, partidx, false);
/*
* If there is an ON CONFLICT clause, initialize state for it.
@@ -910,15 +850,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
}
- /*
- * Also, if transition capture is required, store a map to convert tuples
- * from partition's rowtype to the root partition table's.
- */
- if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
- leaf_part_rri->ri_ChildToRootMap =
- convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(rootResultRelInfo->ri_RelationDesc));
-
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
@@ -949,7 +880,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
PartitionTupleRouting *proute,
PartitionDispatch dispatch,
ResultRelInfo *partRelInfo,
- int partidx)
+ int partidx, bool is_update_rel)
{
ResultRelInfo *rootRelInfo = partRelInfo->ri_RootResultRelInfo;
MemoryContext oldcxt;
@@ -1029,6 +960,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
proute->max_partitions = 8;
proute->partitions = (ResultRelInfo **)
palloc(sizeof(ResultRelInfo *) * proute->max_partitions);
+ proute->is_update_rel = (bool *)
+ palloc(sizeof(bool) * proute->max_partitions);
}
else
{
@@ -1036,10 +969,14 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
proute->partitions = (ResultRelInfo **)
repalloc(proute->partitions, sizeof(ResultRelInfo *) *
proute->max_partitions);
+ proute->is_update_rel = (bool *)
+ repalloc(proute->is_update_rel, sizeof(bool) *
+ proute->max_partitions);
}
}
proute->partitions[rri_index] = partRelInfo;
+ proute->is_update_rel[rri_index] = is_update_rel;
dispatch->indexes[partidx] = rri_index;
MemoryContextSwitchTo(oldcxt);
@@ -1199,7 +1136,6 @@ void
ExecCleanupTupleRouting(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
- HTAB *htab = proute->subplan_resultrel_htab;
int i;
/*
@@ -1230,20 +1166,11 @@ ExecCleanupTupleRouting(ModifyTableState *mtstate,
resultRelInfo);
/*
- * Check if this result rel is one belonging to the node's subplans,
- * if so, let ExecEndPlan() clean it up.
+ * Close it if not one of the result relations borrowed from the owning
+ * ModifyTableState, because those are closed by ExecEndPlan().
*/
- if (htab)
- {
- Oid partoid;
- bool found;
-
- partoid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
-
- (void) hash_search(htab, &partoid, HASH_FIND, &found);
- if (found)
- continue;
- }
+ if (proute->is_update_rel[i])
+ continue;
ExecCloseIndices(resultRelInfo);
table_close(resultRelInfo->ri_RelationDesc, NoLock);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 42632cb4d8..dbaef76448 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1323,3 +1323,26 @@ ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate)
return bms_union(ExecGetUpdatedCols(relinfo, estate),
ExecGetExtraUpdatedCols(relinfo, estate));
}
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * query's main target ("root") relation's format, possibly initializing it
+ * if not already done.
+ */
+TupleConversionMap *
+ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid &&
+ resultRelInfo->ri_RootResultRelInfo)
+ {
+ ResultRelInfo *targetRelInfo;
+
+ targetRelInfo = resultRelInfo->ri_RootResultRelInfo;
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ RelationGetDescr(targetRelInfo->ri_RelationDesc));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index bf65785e64..01eef7b468 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -190,6 +190,361 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
return ExecProject(projectReturning);
}
+/*
+ * ExecGetResultRelation
+ * Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ * if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti;
+ ResultRelInfo *resultRelInfo = NULL;
+
+ /*
+ * Initialized result relations are added to es_result_relations, so check
+ * there first. Remember that es_result_relations is indexed by RT index,
+ * so fetch the relation's RT index from the plan.
+ */
+ Assert(plan != NULL);
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nrels);
+ rti = list_nth_int(plan->resultRelations, whichrel);
+ if (estate->es_result_relations)
+ resultRelInfo = estate->es_result_relations[rti - 1];
+
+ if (resultRelInfo == NULL)
+ {
+ /* Nope, so initialize. */
+ int eflags = estate->es_top_eflags;
+ CmdType operation = mtstate->operation;
+ PlanState *subplanstate = outerPlanState(mtstate);
+ Plan *subplan = subplanstate->plan;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ Assert(whichrel >= 0);
+ resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+ /* Things built here have to last for the query duration. */
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Perform InitResultRelInfo() and save the pointer in
+ * es_result_relations.
+ */
+ ExecInitResultRelation(estate, resultRelInfo, rti);
+
+ /*
+ * A few more initializations that are not handled by
+ * InitResultRelInfo() follow.
+ */
+
+ /*
+ * Verify result relation is a valid target for the current operation.
+ */
+ CheckValidResultRel(resultRelInfo, operation);
+
+ /*
+ * For child result relations, store the root result relation pointer
+ * to be used in places where the mtstate is not available.
+ */
+ if (resultRelInfo != mtstate->rootResultRelInfo)
+ resultRelInfo->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
+
+ /* Initialize the usesFdwDirectModify flag */
+ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+ plan->fdwDirectModifyPlans);
+
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (!resultRelInfo->ri_usesFdwDirectModify &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+ whichrel);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ whichrel,
+ eflags);
+ }
+
+ /* Initilize WITH CHECK OPTIONS expressions. */
+ if (plan->withCheckOptionLists)
+ {
+ List *wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ &mtstate->ps);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
+ /* Initilize RETURNING expressions. */
+ if (plan->returningLists)
+ {
+ List *rlist;
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ rlist = (List *) list_nth(plan->returningLists, whichrel);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+ Assert(slot != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(econtext != NULL);
+
+ resultRelInfo->ri_returningList = rlist;
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ }
+
+ /*
+ * Determine if the FDW supports batch insert and determine the batch
+ * size (a FDW may support batching, but it may be disabled for the
+ * server/table).
+ *
+ * We only do this for INSERT, so that for UPDATE/DELETE the batch
+ * size remains set to 0.
+ */
+ if (operation == CMD_INSERT)
+ {
+ 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;
+
+ Assert(resultRelInfo->ri_BatchSize >= 1);
+ }
+
+ /* Set the list of arbiter indexes if needed for ON CONFLICT */
+ if (plan->onConflictAction != ONCONFLICT_NONE)
+ resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+ /*
+ * If needed, Initialize target list, projection and qual for ON CONFLICT
+ * DO UPDATE.
+ */
+ if (plan->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ ExprContext *econtext;
+ TupleDesc relationDesc;
+ TupleDesc tupDesc;
+
+ /*
+ * insert may only have one relation, inheritance is not expanded.
+ */
+ Assert(mtstate->mt_nrels == 1);
+
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+ /* create state for DO UPDATE SET operation */
+ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+ /* initialize slot for the existing tuple */
+ resultRelInfo->ri_onConflict->oc_Existing =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+ resultRelInfo->ri_onConflict->oc_ProjSlot =
+ ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+ table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+ /* build UPDATE SET projection state */
+ resultRelInfo->ri_onConflict->oc_ProjInfo =
+ ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+ resultRelInfo->ri_onConflict->oc_ProjSlot,
+ &mtstate->ps,
+ relationDesc);
+
+ /* initialize state to evaluate the WHERE clause, if any */
+ if (plan->onConflictWhere)
+ {
+ ExprState *qualexpr;
+
+ qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+ &mtstate->ps);
+ resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ }
+ }
+
+ /*
+ * Initialize projection(s) to create tuples suitable for result
+ * rel(s). INSERT queries may need a projection to filter out junk
+ * attrs in the tlist. UPDATE always needs a projection, because (1)
+ * there's always some junk attrs, and (2) we may need to merge values
+ * of not-updated columns from the old tuple into the final tuple. In
+ * UPDATE, the tuple arriving from the subplan contains only new values
+ * for the changed columns, plus row identity info in the junk attrs.
+ *
+ * If there are multiple result relations, each one needs its own
+ * projection. Note multiple rels are only possible for UPDATE/DELETE,
+ * so we can't be fooled by some needing a projection and some not.
+ *
+ * This section of code is also a convenient place to verify that the
+ * output of an INSERT or UPDATE matches the target table(s).
+ */
+
+ /*
+ * Prepare to generate tuples suitable for the target relation.
+ */
+ if (operation == CMD_INSERT)
+ {
+ List *insertTargetList = NIL;
+ bool need_projection = false;
+
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (!tle->resjunk)
+ insertTargetList = lappend(insertTargetList, tle);
+ else
+ need_projection = true;
+ }
+
+ /*
+ * The junk-free list must produce a tuple suitable for the result
+ * relation.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ insertTargetList);
+
+ /* We'll need a slot matching the table's format. */
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* Build ProjectionInfo if needed (it probably isn't). */
+ if (need_projection)
+ {
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildProjectionInfo(insertTargetList,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps,
+ relDesc);
+ }
+ }
+ else if (operation == CMD_UPDATE)
+ {
+ List *updateColnos;
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ updateColnos = (List *) list_nth(plan->updateColnosLists,
+ whichrel);
+
+ /*
+ * For UPDATE, we use the old tuple to fill up missing values in
+ * the tuple produced by the plan to get the new tuple. We need
+ * two slots, both matching the table's desired format.
+ */
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildUpdateProjection(subplan->targetlist,
+ updateColnos,
+ relDesc,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps);
+ }
+
+ /*
+ * For UPDATE/DELETE, find the appropriate junk attr now, either a
+ * 'ctid' or 'wholerow' attribute depending on relkind. For foreign
+ * tables, the FDW might have created additional junk attr(s), but
+ * those are no concern of ours.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be a
+ * wholerow attribute. We also require it to be present in
+ * UPDATE, so we can get the values of unchanged columns.
+ */
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "wholerow");
+ if (mtstate->operation == CMD_UPDATE &&
+ !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ else
+ {
+ /* Other valid target relkinds must provide wholerow */
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "wholerow");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ return resultRelInfo;
+}
+
/*
* ExecCheckTupleVisible -- verify tuple is visible
*
@@ -488,6 +843,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -641,7 +999,7 @@ ExecInsert(ModifyTableState *mtstate,
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
- (resultRelInfo->ri_RootResultRelInfo == NULL ||
+ (resultRelInfo->ri_RangeTableIndex != 0 ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);
@@ -1276,7 +1634,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;
@@ -1295,13 +1652,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1364,7 +1735,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,
@@ -1434,6 +1805,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1547,6 +1921,13 @@ lreplace:;
*retry_slot;
bool retry;
+ /*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
/*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
@@ -2148,7 +2529,7 @@ ExecModifyTable(PlanState *pstate)
ModifyTableState *node = castNode(ModifyTableState, pstate);
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ResultRelInfo *resultRelInfo;
+ ResultRelInfo *resultRelInfo = NULL;
PlanState *subplanstate;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
@@ -2194,7 +2575,6 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_lastResultIndex;
subplanstate = outerPlanState(node);
/*
@@ -2244,39 +2624,14 @@ 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;
+ resultRelInfo = ExecLookupResultRelByOid(node, resultoid,
+ false);
+ }
- 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;
- }
- }
- }
+ if (resultRelInfo == NULL)
+ resultRelInfo = ExecGetResultRelation(node,
+ node->mt_lastResultIndex);
+ Assert(resultRelInfo != NULL);
/*
* If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do
@@ -2477,12 +2832,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan = outerPlan(node);
CmdType operation = node->operation;
int nrels = list_length(node->resultRelations);
- ResultRelInfo *resultRelInfo;
List *arowmarks;
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)));
@@ -2503,6 +2856,39 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nrels * sizeof(ResultRelInfo));
+ /* Initialize some global state for RETURNING projections. */
+ if (node->returningLists)
+ {
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
+ /*
+ * Initialize the subplan. Must do before allocating any ResultRelInfos.
+ */
+ outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
+
/*----------
* Resolve the target relation. This is the same as:
*
@@ -2511,13 +2897,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* must be converted, and
* - the root partitioned table used for tuple routing.
*
- * If it's a partitioned table, the root partition doesn't appear
- * elsewhere in the plan and its RT index is given explicitly in
- * node->rootRelation. Otherwise (i.e. table inheritance) the target
- * relation is the first relation in the node->resultRelations list.
+ * If it's a partitioned table, given that its RT index doesn't appear in
+ * node->resultRelations unless for an INSERT, the node->resultRelInfo
+ * array doesn't have a slot for it, so we must allocate a ResultRelInfo
+ * for it separately. It need not be initialized fully, so it suffices
+ * to perform just ExecInitResultRelation(). In other cases, including
+ * the case of INSERT into a partitioned table, initialize the first
+ * element of node->resultRelInfo corresponding to the first relation in
+ * node->resultRelations, using ExecGetResultRelation() so that all the
+ * necessary initializations are performed.
*----------
*/
- if (node->rootRelation > 0)
+ if (node->rootRelation > 0 && operation != CMD_INSERT)
{
mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
@@ -2526,8 +2917,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
else
{
mtstate->rootResultRelInfo = mtstate->resultRelInfo;
- ExecInitResultRelation(estate, mtstate->resultRelInfo,
- linitial_int(node->resultRelations));
+ (void) ExecGetResultRelation(mtstate, 0);
}
/* set up epqstate with dummy subplan data for the moment */
@@ -2541,266 +2931,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
ExecSetupTransitionCaptureState(mtstate, estate);
- /*
- * Open all the result relations and initialize the ResultRelInfo structs.
- * (But root relation was initialized above, if it's part of the array.)
- * We must do this before initializing the subplan, because direct-modify
- * FDWs expect their ResultRelInfos to be available.
- */
- resultRelInfo = mtstate->resultRelInfo;
- i = 0;
- foreach(l, node->resultRelations)
- {
- Index resultRelation = lfirst_int(l);
-
- if (resultRelInfo != mtstate->rootResultRelInfo)
- ExecInitResultRelation(estate, resultRelInfo, resultRelation);
-
- /* Initialize the usesFdwDirectModify flag */
- resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
- node->fdwDirectModifyPlans);
-
- /*
- * Verify result relation is a valid target for the current operation
- */
- CheckValidResultRel(resultRelInfo, operation);
-
- resultRelInfo++;
- i++;
- }
-
- /*
- * Now we may initialize the subplan.
- */
- outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
-
- /*
- * Do additional per-result-relation initialization.
- */
- for (i = 0; i < nrels; i++)
- {
- 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 */
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
- {
- List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
- resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
- resultRelInfo,
- fdw_private,
- 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 an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
- /* Set the list of arbiter indexes if needed for ON CONFLICT */
- resultRelInfo = mtstate->resultRelInfo;
- if (node->onConflictAction != ONCONFLICT_NONE)
- resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
- /*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- {
- ExprContext *econtext;
- 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);
-
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
- /* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
- */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
- /* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
- {
- ExprState *qualexpr;
-
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
- }
- }
+ ExecSetupPartitionTupleRouting(estate, rel);
/*
* If we have any secondary relations in an UPDATE or DELETE, they need to
@@ -2827,151 +2969,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks);
- /*
- * Initialize projection(s) to create tuples suitable for result rel(s).
- * INSERT queries may need a projection to filter out junk attrs in the
- * tlist. UPDATE always needs a projection, because (1) there's always
- * some junk attrs, and (2) we may need to merge values of not-updated
- * columns from the old tuple into the final tuple. In UPDATE, the tuple
- * arriving from the subplan contains only new values for the changed
- * columns, plus row identity info in the junk attrs.
- *
- * If there are multiple result relations, each one needs its own
- * projection. Note multiple rels are only possible for UPDATE/DELETE, so
- * we can't be fooled by some needing a projection and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- for (i = 0; i < nrels; i++)
- {
- resultRelInfo = &mtstate->resultRelInfo[i];
-
- /*
- * Prepare to generate tuples suitable for the target relation.
- */
- if (operation == CMD_INSERT)
- {
- List *insertTargetList = NIL;
- bool need_projection = false;
-
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (!tle->resjunk)
- insertTargetList = lappend(insertTargetList, tle);
- else
- need_projection = true;
- }
-
- /*
- * The junk-free list must produce a tuple suitable for the result
- * relation.
- */
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- insertTargetList);
-
- /* We'll need a slot matching the table's format. */
- resultRelInfo->ri_newTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* Build ProjectionInfo if needed (it probably isn't). */
- if (need_projection)
- {
- TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
-
- /* need an expression context to do the projection */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- resultRelInfo->ri_projectNew =
- ExecBuildProjectionInfo(insertTargetList,
- mtstate->ps.ps_ExprContext,
- resultRelInfo->ri_newTupleSlot,
- &mtstate->ps,
- relDesc);
- }
- }
- else if (operation == CMD_UPDATE)
- {
- List *updateColnos;
- TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
-
- updateColnos = (List *) list_nth(node->updateColnosLists, i);
-
- /*
- * For UPDATE, we use the old tuple to fill up missing values in
- * the tuple produced by the plan to get the new tuple. We need
- * two slots, both matching the table's desired format.
- */
- resultRelInfo->ri_oldTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
- resultRelInfo->ri_newTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* need an expression context to do the projection */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- resultRelInfo->ri_projectNew =
- ExecBuildUpdateProjection(subplan->targetlist,
- updateColnos,
- relDesc,
- mtstate->ps.ps_ExprContext,
- resultRelInfo->ri_newTupleSlot,
- &mtstate->ps);
- }
-
- /*
- * For UPDATE/DELETE, find the appropriate junk attr now, either a
- * 'ctid' or 'wholerow' attribute depending on relkind. For foreign
- * tables, the FDW might have created additional junk attr(s), but
- * those are no concern of ours.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- resultRelInfo->ri_RowIdAttNo =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
- if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be a
- * wholerow attribute. We also require it to be present in
- * UPDATE, so we can get the values of unchanged columns.
- */
- resultRelInfo->ri_RowIdAttNo =
- ExecFindJunkAttributeInTlist(subplan->targetlist,
- "wholerow");
- if (mtstate->operation == CMD_UPDATE &&
- !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- else
- {
- /* Other valid target relkinds must provide wholerow */
- resultRelInfo->ri_RowIdAttNo =
- ExecFindJunkAttributeInTlist(subplan->targetlist,
- "wholerow");
- if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
- }
-
/*
* If this is an inherited update/delete, there will be a junk attribute
* named "tableoid" present in the subplan's targetlist. It will be used
@@ -2984,6 +2981,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_lastResultOid = InvalidOid; /* force lookup at first tuple */
mtstate->mt_lastResultIndex = 0; /* must be zero if no such attr */
+ /*
+ * While we normally don't allocate ResultRelInfos for a child relation
+ * until ExecModifyTable() gets a tuple to update/delete from the subplan
+ * that belongs to that relation, that strategy is futile for child
+ * relations that are foreign tables whose update/delete have been pushed
+ * to the remote side. The ForeignScan node which performs a given
+ * "direct" update/delete operation expects that the ResultRelInfo for the
+ * foreign table has been initialized and placed in
+ * estate->es_result_relations[], so we allocate them here.
+ */
+ i = -1;
+ while((i = bms_next_member(node->fdwDirectModifyPlans, i)) >= 0)
+ (void) ExecGetResultRelation(mtstate, i);
+
/*
* If there are a lot of result relations, use a hash table to speed the
* lookups. If there are not a lot, a simple linear search is faster.
@@ -3008,49 +3019,33 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
hash_create("ModifyTable target hash",
nrels, &hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
- for (i = 0; i < nrels; i++)
+ i = 0;
+ foreach(l, node->resultRelations)
{
- Oid hashkey;
+ Index resultRelation = lfirst_int(l);
+ Oid hashkey = exec_rt_fetch(resultRelation, estate)->relid;
MTTargetRelLookup *mtlookup;
bool found;
- resultRelInfo = &mtstate->resultRelInfo[i];
- hashkey = RelationGetRelid(resultRelInfo->ri_RelationDesc);
mtlookup = (MTTargetRelLookup *)
hash_search(mtstate->mt_resultOidHash, &hashkey,
HASH_ENTER, &found);
Assert(!found);
- mtlookup->relationIndex = i;
+ mtlookup->relationIndex = i++;
}
+ mtstate->mt_resultOidArray = NULL;
}
else
- mtstate->mt_resultOidHash = NULL;
-
- /*
- * Determine if the FDW supports batch insert and determine the batch
- * size (a FDW may support batching, but it may be disabled for the
- * server/table).
- *
- * We only do this for INSERT, so that for UPDATE/DELETE the batch
- * size remains set to 0.
- */
- if (operation == CMD_INSERT)
{
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nrels; i++)
+ mtstate->mt_resultOidHash = NULL;
+ mtstate->mt_resultOidArray = (Oid *) palloc(nrels * sizeof(Oid));
+ i = 0;
+ foreach(l, node->resultRelations)
{
- 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;
+ Index resultRelation = lfirst_int(l);
- Assert(resultRelInfo->ri_BatchSize >= 1);
-
- resultRelInfo++;
+ mtstate->mt_resultOidArray[i++] =
+ exec_rt_fetch(resultRelation, estate)->relid;
}
}
@@ -3081,22 +3076,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
void
ExecEndModifyTable(ModifyTableState *node)
{
- int i;
-
- /*
- * Allow any FDWs to shut down
- */
- for (i = 0; i < node->mt_nrels; i++)
- {
- ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
- if (!resultRelInfo->ri_usesFdwDirectModify &&
- resultRelInfo->ri_FdwRoutine != NULL &&
- resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
- resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
- resultRelInfo);
- }
-
/*
* Close all the partitioned tables, leaf partitions, and their indices
* and release the slot used for tuple routing, if set.
@@ -3140,3 +3119,62 @@ ExecReScanModifyTable(ModifyTableState *node)
*/
elog(ERROR, "ExecReScanModifyTable is not implemented");
}
+
+/*
+ * ExecLookupResultRelByOid
+ * If the table with given OID is among the result relations to be
+ * updated by the given ModifyTable node, return its ResultRelInfo, NULL
+ * otherwise.
+ */
+ResultRelInfo *
+ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid,
+ bool missing_ok)
+{
+ bool found;
+
+ 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,
+ &found);
+ if (found)
+ {
+ Assert(mtlookup != NULL);
+ node->mt_lastResultOid = resultoid;
+ node->mt_lastResultIndex = mtlookup->relationIndex;
+ }
+ else if (!missing_ok)
+ elog(ERROR, "incorrect result rel OID %u", resultoid);
+ }
+ else
+ {
+ /* With few target rels, search in the pre-built OID array */
+ int ndx;
+
+ found = false;
+ for (ndx = 0; ndx < node->mt_nrels; ndx++)
+ {
+ if (node->mt_resultOidArray[ndx] == resultoid)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ {
+ Assert(ndx < node->mt_nrels);
+ node->mt_lastResultOid = resultoid;
+ node->mt_lastResultIndex = ndx;
+ }
+ else if (!missing_ok)
+ elog(ERROR, "incorrect result rel OID %u", resultoid);
+ }
+
+ if (!found)
+ return NULL;
+
+ return ExecGetResultRelation(node, node->mt_lastResultIndex);
+}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 354fbe4b4b..e901823b63 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1583,7 +1583,7 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo,
mtstate->ps.state = estate;
mtstate->operation = operation;
mtstate->resultRelInfo = relinfo;
- proute = ExecSetupPartitionTupleRouting(estate, mtstate, parentrel);
+ proute = ExecSetupPartitionTupleRouting(estate, parentrel);
/*
* Find the partition to which the "search tuple" belongs.
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index d30ffde7d9..694e38b7dd 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -111,7 +111,6 @@ typedef struct PartitionPruneState
} PartitionPruneState;
extern PartitionTupleRouting *ExecSetupPartitionTupleRouting(EState *estate,
- ModifyTableState *mtstate,
Relation rel);
extern ResultRelInfo *ExecFindPartition(ModifyTableState *mtstate,
ResultRelInfo *rootResultRelInfo,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 34dd861eff..7516aeffe6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -589,6 +589,7 @@ extern int ExecCleanTargetListLength(List *targetlist);
extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
+extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
@@ -638,9 +639,12 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
-/* needed by trigger.c */
+/* prototypes from nodeModifyTable.c */
extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot);
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel);
+extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid,
+ bool missing_ok);
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3b39369a49..2bfe1d75c4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -515,6 +515,7 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ bool ri_ChildToRootMapValid; /* has the map been initialized? */
/* for use by copyfrom.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
@@ -1179,7 +1180,8 @@ typedef struct ModifyTableState
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
bool mt_done; /* are we done? */
- int mt_nrels; /* number of entries in resultRelInfo[] */
+ int mt_nrels; /* number of entries in resultRelInfo[] and
+ * resultOidArray[] */
ResultRelInfo *resultRelInfo; /* info about target relation(s) */
/*
@@ -1197,12 +1199,13 @@ typedef struct ModifyTableState
* These fields are used for inherited UPDATE and DELETE, to track which
* target relation a given tuple is from. If there are a lot of target
* relations, we use a hash table to translate table OIDs to
- * resultRelInfo[] indexes; otherwise mt_resultOidHash is NULL.
+ * resultRelInfo[] indexes; otherwise an array.
*/
int mt_resultOidAttno; /* resno of "tableoid" junk attr */
Oid mt_lastResultOid; /* last-seen value of tableoid */
int mt_lastResultIndex; /* corresponding index in resultRelInfo[] */
HTAB *mt_resultOidHash; /* optional hash table to speed lookups */
+ Oid *mt_resultOidArray; /* array when hash table is not used */
/*
* Slot for storing tuples in the root partitioned table's rowtype during
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 1c703c351f..06f44287bc 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2492,7 +2492,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 15).
UPDATE errtst_parent SET data = data + 10 WHERE partid = 20;
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (15, 1, 20).
+DETAIL: Failing row contains (20, 1, 15).
-- direct leaf partition update, without partition id violation
BEGIN;
UPDATE errtst_child_fastdef SET partid = 1 WHERE partid = 0;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 89f3d5da46..6372e0ed6a 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -685,7 +685,7 @@ DETAIL: Failing row contains (a, b, c) = (aaa, null, null).
-- simple update.
UPDATE errtst SET b = NULL;
ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint
-DETAIL: Failing row contains (b) = (null).
+DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc).
-- partitioning key is updated, doesn't move the row.
UPDATE errtst SET a = 'aaa', b = NULL;
ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index dc34ac67b3..ad91e5aedb 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -342,8 +342,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -373,8 +373,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -815,8 +815,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
--
2.24.1
On Thu, Apr 1, 2021 at 10:12 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Apr 1, 2021 at 3:12 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Langote <amitlangote09@gmail.com> writes:
[ v14-0002-Initialize-result-relation-information-lazily.patch ]
Needs YA rebase over 86dc90056.
Done. I will post the updated results for -Mprepared benchmarks I did
in the other thread shortly.
Test details:
pgbench -n -T60 -Mprepared -f nojoin.sql
nojoin.sql:
\set a random(1, 1000000)
update test_table t set b = :a where a = :a;
* test_table has 40 columns and partitions as shown below
* plan_cache_mode = force_generic_plan
Results:
nparts master patched
64 6262 17118
128 3449 12082
256 1722 7643
1024 359 2099
* tps figures shown are the median of 3 runs.
So, drastic speedup can be seen by even just not creating
ResultRelInfos for child relations that are not updated, as the patch
does. I haven't yet included any changes for AcquireExecutorLocks()
and ExecCheckRTPerms() bottlenecks that still remain and cause the
drop in tps as partition count increases.
--
Amit Langote
EDB: http://www.enterprisedb.com
Amit Langote <amitlangote09@gmail.com> writes:
On Thu, Apr 1, 2021 at 3:12 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Langote <amitlangote09@gmail.com> writes:
[ v14-0002-Initialize-result-relation-information-lazily.patch ]
Needs YA rebase over 86dc90056.
Done.
I spent some time looking this over. There are bits of it we can
adopt without too much trouble, but I'm afraid that 0001 (delay
FDW BeginDirectModify until the first actual update) is a nonstarter,
which makes the main idea of delaying ExecInitResultRelation unworkable.
My fear about 0001 is that it will destroy any hope of direct updates
on different remote partitions executing with consistent semantics
(i.e. compatible snapshots), because some row updates triggered by the
local query may have already happened before a given partition gets to
start its remote query. Maybe we can work around that, but I do not
want to commit a major restructuring that assumes we can dodge this
problem when we don't yet even have a fix for cross-partition updates
that does rely on the assumption of synchronous startup.
In some desultory performance testing here, it seemed like a
significant part of the cost is ExecOpenIndices, and I don't see
a reason offhand why we could not delay/skip that. I also concur
with delaying construction of ri_ChildToRootMap and the
partition_tuple_routing data structures, since many queries will
never need those at all.
* PartitionTupleRouting.subplan_resultrel_htab is removed in favor
of using ModifyTableState.mt_resultOidHash to look up an UPDATE
result relation by OID.
Hmm, that sounds promising too, though I didn't look at the details.
Anyway, I think the way to proceed for now is to grab the low-hanging
fruit of things that clearly won't change any semantics. But tail end
of the dev cycle is no time to be making really fundamental changes
in how FDW direct modify works.
regards, tom lane
On Sun, Apr 4, 2021 at 10:20 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Langote <amitlangote09@gmail.com> writes:
On Thu, Apr 1, 2021 at 3:12 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Langote <amitlangote09@gmail.com> writes:
[ v14-0002-Initialize-result-relation-information-lazily.patch ]
Needs YA rebase over 86dc90056.
Done.
I spent some time looking this over.
Thanks.
There are bits of it we can
adopt without too much trouble, but I'm afraid that 0001 (delay
FDW BeginDirectModify until the first actual update) is a nonstarter,
which makes the main idea of delaying ExecInitResultRelation unworkable.My fear about 0001 is that it will destroy any hope of direct updates
on different remote partitions executing with consistent semantics
(i.e. compatible snapshots), because some row updates triggered by the
local query may have already happened before a given partition gets to
start its remote query. Maybe we can work around that, but I do not
want to commit a major restructuring that assumes we can dodge this
problem when we don't yet even have a fix for cross-partition updates
that does rely on the assumption of synchronous startup.
Hmm, okay, I can understand the concern.
In some desultory performance testing here, it seemed like a
significant part of the cost is ExecOpenIndices, and I don't see
a reason offhand why we could not delay/skip that. I also concur
with delaying construction of ri_ChildToRootMap and the
partition_tuple_routing data structures, since many queries will
never need those at all.
As I mentioned in [1]/messages/by-id/CA+HiwqHLUNhMxy46Mrb04VJpN=HUdm9bD7xdZ6f5h2o4imX79g@mail.gmail.com, creating ri_projectNew can be expensive too,
especially as column count (and partition count for the generic plan
case) grows. I think we should have an static inline
initialize-on-first-access accessor function for that field too.
Actually, I remember considering having such accessor functions (all
static inline) for ri_WithCheckOptionExprs, ri_projectReturning,
ri_onConflictArbiterIndexes, and ri_onConflict (needed by ON CONFLICT
UPDATE) as well, prompted by Heikki's comments earlier in the
discussion. I also remember, before even writing this patch, not
liking that WCO and RETURNING expressions are initialized in their own
separate loops, rather than being part of the earlier loop that says:
/*
* Do additional per-result-relation initialization.
*/
for (i = 0; i < nrels; i++)
{
I guess ri_RowIdAttNo initialization can go into the same loop.
* PartitionTupleRouting.subplan_resultrel_htab is removed in favor
of using ModifyTableState.mt_resultOidHash to look up an UPDATE
result relation by OID.Hmm, that sounds promising too, though I didn't look at the details.
Anyway, I think the way to proceed for now is to grab the low-hanging
fruit of things that clearly won't change any semantics. But tail end
of the dev cycle is no time to be making really fundamental changes
in how FDW direct modify works.
I agree.
--
Amit Langote
EDB: http://www.enterprisedb.com
[1]: /messages/by-id/CA+HiwqHLUNhMxy46Mrb04VJpN=HUdm9bD7xdZ6f5h2o4imX79g@mail.gmail.com
Amit Langote <amitlangote09@gmail.com> writes:
On Sun, Apr 4, 2021 at 10:20 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
In some desultory performance testing here, it seemed like a
significant part of the cost is ExecOpenIndices, and I don't see
a reason offhand why we could not delay/skip that. I also concur
with delaying construction of ri_ChildToRootMap and the
partition_tuple_routing data structures, since many queries will
never need those at all.
As I mentioned in [1], creating ri_projectNew can be expensive too,
especially as column count (and partition count for the generic plan
case) grows. I think we should have an static inline
initialize-on-first-access accessor function for that field too.
Actually, I remember considering having such accessor functions (all
static inline) for ri_WithCheckOptionExprs, ri_projectReturning,
ri_onConflictArbiterIndexes, and ri_onConflict (needed by ON CONFLICT
UPDATE) as well, prompted by Heikki's comments earlier in the
discussion. I also remember, before even writing this patch, not
liking that WCO and RETURNING expressions are initialized in their own
separate loops, rather than being part of the earlier loop that says:
Sure, we might as well try to improve the cosmetics here.
Anyway, I think the way to proceed for now is to grab the low-hanging
fruit of things that clearly won't change any semantics. But tail end
of the dev cycle is no time to be making really fundamental changes
in how FDW direct modify works.
I agree.
OK. Do you want to pull out the bits of the patch that we can still
do without postponing BeginDirectModify?
Another thing we could consider, perhaps, is keeping the behavior
the same for foreign tables but postponing init of local ones.
To avoid opening the relations to figure out which kind they are,
we'd have to rely on the RTE copies of relkind, which is a bit
worrisome --- I'm not certain that those are guaranteed to be
up-to-date --- but it's probably okay since there is no way to
convert a regular table to foreign or vice versa. Anyway, that
idea seems fairly messy so I'm inclined to just pursue the
lower-hanging fruit for now.
regards, tom lane
On Mon, Apr 5, 2021 at 1:43 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Langote <amitlangote09@gmail.com> writes:
On Sun, Apr 4, 2021 at 10:20 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
In some desultory performance testing here, it seemed like a
significant part of the cost is ExecOpenIndices, and I don't see
a reason offhand why we could not delay/skip that. I also concur
with delaying construction of ri_ChildToRootMap and the
partition_tuple_routing data structures, since many queries will
never need those at all.As I mentioned in [1], creating ri_projectNew can be expensive too,
especially as column count (and partition count for the generic plan
case) grows. I think we should have an static inline
initialize-on-first-access accessor function for that field too.Actually, I remember considering having such accessor functions (all
static inline) for ri_WithCheckOptionExprs, ri_projectReturning,
ri_onConflictArbiterIndexes, and ri_onConflict (needed by ON CONFLICT
UPDATE) as well, prompted by Heikki's comments earlier in the
discussion. I also remember, before even writing this patch, not
liking that WCO and RETURNING expressions are initialized in their own
separate loops, rather than being part of the earlier loop that says:Sure, we might as well try to improve the cosmetics here.
Anyway, I think the way to proceed for now is to grab the low-hanging
fruit of things that clearly won't change any semantics. But tail end
of the dev cycle is no time to be making really fundamental changes
in how FDW direct modify works.I agree.
OK. Do you want to pull out the bits of the patch that we can still
do without postponing BeginDirectModify?
I ended up with the attached, whereby ExecInitResultRelation() is now
performed for all relations before calling ExecInitNode() on the
subplan. As mentioned, I moved other per-result-rel initializations
into the same loop that does ExecInitResultRelation(), while moving
code related to some initializations into initialize-on-first-access
accessor functions for the concerned fields. I chose to do that for
ri_WIthCheckOptionExprs, ri_projectReturning, and ri_projectNew.
ExecInitNode() is called on the subplan (to set
outerPlanState(mtstate) that is) after all of the per-result-rel
initializations are done. One of the initializations is calling
BeginForeignModify() for non-direct modifications, an API to which we
currently pass mtstate. Moving that to before setting
outerPlanState(mtstate) so as to be in the same loop as other
initializations had me worried just a little bit given a modification
I had to perform in postgresBeginForeignModify():
@@ -1879,7 +1879,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
rte,
resultRelInfo,
mtstate->operation,
- outerPlanState(mtstate)->plan,
+ outerPlan(mtstate->ps.plan),
query,
target_attrs,
values_end_len,
Though I think that this is harmless, because I'd think that the
implementers of this API shouldn't really rely too strongly on
assuming that outerPlanState(mtstate) is valid when it is called, if
postgres_fdw's implementation is any indication.
Another slightly ugly bit is the dependence of direct modify API on
ri_projectReturning being set even if it doesn't care for anything
else in the ResultRelInfo. So in ExecInitModifyTable()
ri_projectReturning initialization is not skipped for
directly-modified foreign result relations.
Notes on regression test changes:
* Initializing WCO quals during execution instead of during
ExecInitNode() of ModifyTable() causes a couple of regression test
changes in updatable_view.out that were a bit unexpected for me --
Subplans that are referenced in WCO quals are no longer shown in the
plain EXPLAIN output. Even though that's a user-visible change, maybe
we can live with that?
* ri_RootResultRelInfo in *all* child relations instead of just in
tuple-routing result relations has caused changes to inherit.out and
privileges.out. I think that's basically down to ExecConstraints() et
al doing one thing for child relations in which ri_RootResultRelInfo
is set and another for those in which it is not. Now it's set in
*all* child relations, so it always does the former thing. I remember
having checked that those changes are only cosmetic when I first
encountered them.
* Moving PartitionTupleRouting initialization to be done lazily for
cross-partition update cases causes changes to update.out. They have
to do with the fact that the violations of the actual target table's
partition constraint are now shown as such, instead of reporting them
as occurring on one of the leaf partitions. Again, only cosmetic.
Another thing we could consider, perhaps, is keeping the behavior
the same for foreign tables but postponing init of local ones.
To avoid opening the relations to figure out which kind they are,
we'd have to rely on the RTE copies of relkind, which is a bit
worrisome --- I'm not certain that those are guaranteed to be
up-to-date --- but it's probably okay since there is no way to
convert a regular table to foreign or vice versa. Anyway, that
idea seems fairly messy so I'm inclined to just pursue the
lower-hanging fruit for now.
It would be nice to try that idea out, but I tend to agree with the last part.
Also, I'm fairly happy with the kind of performance improvement I see
even with the lower-hanging fruit patch for my earlier earlier shared
benchmark that tests the performance of generic plan execution:
HEAD (especially with 86dc90056 now in):
nparts 10cols 20cols 40cols
64 6926 6394 6253
128 3758 3501 3482
256 1938 1822 1776
1024 406 371 406
Patched:
64 13147 12554 14787
128 7850 9788 9631
256 4472 5599 5638
1024 1218 1503 1309
I also tried with a version where the new tuple projections are built
in ExecInitModifyTable() as opposed to lazily:
64 10937 9969 8535
128 6586 5903 4887
256 3613 3118 2654
1024 884 749 652
This tells us that delaying initializing new tuple projection for
updates can have a sizable speedup and better scalability.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
v16-0001-Initialize-result-relation-information-lazily.patchapplication/octet-stream; name=v16-0001-Initialize-result-relation-information-lazily.patchDownload
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 16c2979f2d..5f6d0fa1bf 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1879,7 +1879,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
rte,
resultRelInfo,
mtstate->operation,
- outerPlanState(mtstate)->plan,
+ outerPlan(mtstate->ps.plan),
query,
target_attrs,
values_end_len,
@@ -1983,7 +1983,7 @@ postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo)
batch_size = get_batch_size_option(resultRelInfo->ri_RelationDesc);
/* Disable batching when we have to use RETURNING. */
- if (resultRelInfo->ri_projectReturning != NULL ||
+ if (resultRelInfo->ri_returningList != NULL ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_after_row))
return 1;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index be2e3d7354..20e7d57d41 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -697,7 +697,7 @@ CopyFrom(CopyFromState cstate)
* CopyFrom tuple routing.
*/
if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
- proute = ExecSetupPartitionTupleRouting(estate, NULL, cstate->rel);
+ proute = ExecSetupPartitionTupleRouting(estate, cstate->rel);
if (cstate->whereClause)
cstate->qualexpr = ExecInitQual(castNode(List, cstate->whereClause),
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index a5ceb1698c..3421014e47 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -5479,7 +5479,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
- TupleConversionMap *map = relinfo->ri_ChildToRootMap;
+ TupleConversionMap *map = ExecGetChildToRootMap(relinfo);
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 163242f54e..bd457254d0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1221,6 +1221,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_projectNew = NULL;
resultRelInfo->ri_newTupleSlot = NULL;
resultRelInfo->ri_oldTupleSlot = NULL;
+ resultRelInfo->ri_projectNewInfoValid = false;
resultRelInfo->ri_FdwState = NULL;
resultRelInfo->ri_usesFdwDirectModify = false;
resultRelInfo->ri_ConstraintExprs = NULL;
@@ -1231,11 +1232,17 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ReturningSlot = NULL;
resultRelInfo->ri_TrigOldSlot = NULL;
resultRelInfo->ri_TrigNewSlot = NULL;
+ /*
+ * Only ExecInitPartitionInfo() passes partition_root_rri. For child
+ * relations that are not tuple routing target relations, this is set in
+ * ExecInitModifyTable().
+ */
resultRelInfo->ri_RootResultRelInfo = partition_root_rri;
resultRelInfo->ri_RootToPartitionMap = NULL; /* set by
* ExecInitRoutingInfo */
resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */
resultRelInfo->ri_ChildToRootMap = NULL;
+ resultRelInfo->ri_ChildToRootMapValid = false;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}
@@ -1923,7 +1930,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
*/
void
ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
- TupleTableSlot *slot, EState *estate)
+ TupleTableSlot *slot, EState *estate,
+ PlanState *parent)
{
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
@@ -1931,6 +1939,22 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
ListCell *l1,
*l2;
+ /* Initialize the quals if not already done. */
+ if (resultRelInfo->ri_WithCheckOptionExprs == NIL)
+ {
+ List *wcoExprs = NIL;
+
+ foreach(l1, resultRelInfo->ri_WithCheckOptions)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(l1);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ parent);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
/*
* We will use the EState's per-tuple context for evaluating constraint
* expressions (creating it if it's not already there).
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 558060e080..ead630a367 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -68,9 +68,15 @@
* Array of 'max_partitions' elements containing a pointer to a
* ResultRelInfo for every leaf partitions touched by tuple routing.
* Some of these are pointers to ResultRelInfos which are borrowed out of
- * 'subplan_resultrel_htab'. The remainder have been built especially
- * for tuple routing. See comment for PartitionDispatchData->indexes for
- * details on how this array is indexed.
+ * the owning ModifyTableState node. The remainder have been built
+ * especially for tuple routing. See comment for
+ * PartitionDispatchData->indexes for details on how this array is
+ * indexed.
+ *
+ * is_update_rel
+ * Array of 'max_partitions' booleans recording whether a given entry
+ * in 'partitions' is a ResultRelInfo pointer borrowed from a matching
+ * UPDATE result relation in the owning ModifyTableState node
*
* num_partitions
* The current number of items stored in the 'partitions' array. Also
@@ -80,12 +86,6 @@
* max_partitions
* The current allocated size of the 'partitions' array.
*
- * subplan_resultrel_htab
- * Hash table to store subplan ResultRelInfos by Oid. This is used to
- * cache ResultRelInfos from targets of an UPDATE ModifyTable node;
- * NULL in other cases. Some of these may be useful for tuple routing
- * to save having to build duplicates.
- *
* memcxt
* Memory context used to allocate subsidiary structs.
*-----------------------
@@ -98,9 +98,9 @@ struct PartitionTupleRouting
int num_dispatch;
int max_dispatch;
ResultRelInfo **partitions;
+ bool *is_update_rel;
int num_partitions;
int max_partitions;
- HTAB *subplan_resultrel_htab;
MemoryContext memcxt;
};
@@ -153,16 +153,7 @@ typedef struct PartitionDispatchData
int indexes[FLEXIBLE_ARRAY_MEMBER];
} PartitionDispatchData;
-/* struct to hold result relations coming from UPDATE subplans */
-typedef struct SubplanResultRelHashElem
-{
- Oid relid; /* hash key -- must be first */
- ResultRelInfo *rri;
-} SubplanResultRelHashElem;
-
-static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
- PartitionTupleRouting *proute);
static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
EState *estate, PartitionTupleRouting *proute,
PartitionDispatch dispatch,
@@ -173,7 +164,7 @@ static void ExecInitRoutingInfo(ModifyTableState *mtstate,
PartitionTupleRouting *proute,
PartitionDispatch dispatch,
ResultRelInfo *partRelInfo,
- int partidx);
+ int partidx, bool is_update_rel);
static PartitionDispatch ExecInitPartitionDispatchInfo(EState *estate,
PartitionTupleRouting *proute,
Oid partoid, PartitionDispatch parent_pd,
@@ -215,11 +206,9 @@ static void find_matching_subplans_recurse(PartitionPruningData *prunedata,
* it should be estate->es_query_cxt.
*/
PartitionTupleRouting *
-ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
- Relation rel)
+ExecSetupPartitionTupleRouting(EState *estate, Relation rel)
{
PartitionTupleRouting *proute;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
/*
* Here we attempt to expend as little effort as possible in setting up
@@ -241,17 +230,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
NULL, 0, NULL);
- /*
- * If performing an UPDATE with tuple routing, we can reuse partition
- * sub-plan result rels. We build a hash table to map the OIDs of
- * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
- * Every time a tuple is routed to a partition that we've yet to set the
- * ResultRelInfo for, before we go to the trouble of making one, we check
- * for a pre-made one in the hash table.
- */
- if (node && node->operation == CMD_UPDATE)
- ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
return proute;
}
@@ -351,7 +329,6 @@ ExecFindPartition(ModifyTableState *mtstate,
is_leaf = partdesc->is_leaf[partidx];
if (is_leaf)
{
-
/*
* We've reached the leaf -- hurray, we're done. Look to see if
* we've already got a ResultRelInfo for this partition.
@@ -368,20 +345,18 @@ ExecFindPartition(ModifyTableState *mtstate,
/*
* We have not yet set up a ResultRelInfo for this partition,
- * but if we have a subplan hash table, we might have one
- * there. If not, we'll have to create one.
+ * but if the partition is also an UPDATE result relation, use
+ * the one in mtstate->resultRelInfo instead of creating a new
+ * one with ExecInitPartitionInfo().
*/
- if (proute->subplan_resultrel_htab)
+ if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
{
Oid partoid = partdesc->oids[partidx];
- SubplanResultRelHashElem *elem;
- elem = hash_search(proute->subplan_resultrel_htab,
- &partoid, HASH_FIND, NULL);
- if (elem)
+ rri = ExecLookupResultRelByOid(mtstate, partoid, true);
+ if (rri)
{
found = true;
- rri = elem->rri;
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT);
@@ -391,7 +366,7 @@ ExecFindPartition(ModifyTableState *mtstate,
* subsequent tuples routed to this partition.
*/
ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
- rri, partidx);
+ rri, partidx, true);
}
}
@@ -509,50 +484,6 @@ ExecFindPartition(ModifyTableState *mtstate,
return rri;
}
-/*
- * ExecHashSubPlanResultRelsByOid
- * Build a hash table to allow fast lookups of subplan ResultRelInfos by
- * partition Oid. We also populate the subplan ResultRelInfo with an
- * ri_PartitionRoot.
- */
-static void
-ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
- PartitionTupleRouting *proute)
-{
- HASHCTL ctl;
- HTAB *htab;
- int i;
-
- ctl.keysize = sizeof(Oid);
- ctl.entrysize = sizeof(SubplanResultRelHashElem);
- ctl.hcxt = CurrentMemoryContext;
-
- htab = hash_create("PartitionTupleRouting table", mtstate->mt_nrels,
- &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
- proute->subplan_resultrel_htab = htab;
-
- /* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nrels; i++)
- {
- ResultRelInfo *rri = &mtstate->resultRelInfo[i];
- bool found;
- Oid partoid = RelationGetRelid(rri->ri_RelationDesc);
- SubplanResultRelHashElem *elem;
-
- elem = (SubplanResultRelHashElem *)
- hash_search(htab, &partoid, HASH_ENTER, &found);
- Assert(!found);
- elem->rri = rri;
-
- /*
- * This is required in order to convert the partition's tuple to be
- * compatible with the root partitioned table's tuple descriptor. When
- * generating the per-subplan result rels, this was not set.
- */
- rri->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
- }
-}
-
/*
* ExecInitPartitionInfo
* Lock the partition and initialize ResultRelInfo. Also setup other
@@ -613,7 +544,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* didn't build the withCheckOptionList for partitions within the planner,
* but simple translation of varattnos will suffice. This only occurs for
* the INSERT case or in the case of UPDATE tuple routing where we didn't
- * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+ * find a result rel to reuse.
*/
if (node && node->withCheckOptionLists != NIL)
{
@@ -680,8 +611,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->returningLists != NIL)
{
- TupleTableSlot *slot;
- ExprContext *econtext;
List *returningList;
/* See the comment above for WCO lists. */
@@ -716,25 +645,11 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/* We ignore the value of found_whole_row. */
leaf_part_rri->ri_returningList = returningList;
-
- /*
- * Initialize the projection itself.
- *
- * Use the slot and the expression context that would have been set up
- * in ExecInitModifyTable() for projection's output.
- */
- Assert(mtstate->ps.ps_ResultTupleSlot != NULL);
- slot = mtstate->ps.ps_ResultTupleSlot;
- Assert(mtstate->ps.ps_ExprContext != NULL);
- econtext = mtstate->ps.ps_ExprContext;
- leaf_part_rri->ri_projectReturning =
- ExecBuildProjectionInfo(returningList, econtext, slot,
- &mtstate->ps, RelationGetDescr(partrel));
}
/* Set up information needed for routing tuples to the partition. */
ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
- leaf_part_rri, partidx);
+ leaf_part_rri, partidx, false);
/*
* If there is an ON CONFLICT clause, initialize state for it.
@@ -910,15 +825,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
}
- /*
- * Also, if transition capture is required, store a map to convert tuples
- * from partition's rowtype to the root partition table's.
- */
- if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
- leaf_part_rri->ri_ChildToRootMap =
- convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
- RelationGetDescr(rootResultRelInfo->ri_RelationDesc));
-
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
@@ -949,7 +855,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
PartitionTupleRouting *proute,
PartitionDispatch dispatch,
ResultRelInfo *partRelInfo,
- int partidx)
+ int partidx, bool is_update_rel)
{
ResultRelInfo *rootRelInfo = partRelInfo->ri_RootResultRelInfo;
MemoryContext oldcxt;
@@ -1029,6 +935,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
proute->max_partitions = 8;
proute->partitions = (ResultRelInfo **)
palloc(sizeof(ResultRelInfo *) * proute->max_partitions);
+ proute->is_update_rel = (bool *)
+ palloc(sizeof(bool) * proute->max_partitions);
}
else
{
@@ -1036,10 +944,14 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
proute->partitions = (ResultRelInfo **)
repalloc(proute->partitions, sizeof(ResultRelInfo *) *
proute->max_partitions);
+ proute->is_update_rel = (bool *)
+ repalloc(proute->is_update_rel, sizeof(bool) *
+ proute->max_partitions);
}
}
proute->partitions[rri_index] = partRelInfo;
+ proute->is_update_rel[rri_index] = is_update_rel;
dispatch->indexes[partidx] = rri_index;
MemoryContextSwitchTo(oldcxt);
@@ -1199,7 +1111,6 @@ void
ExecCleanupTupleRouting(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
- HTAB *htab = proute->subplan_resultrel_htab;
int i;
/*
@@ -1230,20 +1141,11 @@ ExecCleanupTupleRouting(ModifyTableState *mtstate,
resultRelInfo);
/*
- * Check if this result rel is one belonging to the node's subplans,
- * if so, let ExecEndPlan() clean it up.
+ * Close it if not one of the result relations borrowed from the owning
+ * ModifyTableState, because those are closed by ExecEndPlan().
*/
- if (htab)
- {
- Oid partoid;
- bool found;
-
- partoid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
-
- (void) hash_search(htab, &partoid, HASH_FIND, &found);
- if (found)
- continue;
- }
+ if (proute->is_update_rel[i])
+ continue;
ExecCloseIndices(resultRelInfo);
table_close(resultRelInfo->ri_RelationDesc, NoLock);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 42632cb4d8..dbaef76448 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1323,3 +1323,26 @@ ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate)
return bms_union(ExecGetUpdatedCols(relinfo, estate),
ExecGetExtraUpdatedCols(relinfo, estate));
}
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * query's main target ("root") relation's format, possibly initializing it
+ * if not already done.
+ */
+TupleConversionMap *
+ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
+{
+ if (!resultRelInfo->ri_ChildToRootMapValid &&
+ resultRelInfo->ri_RootResultRelInfo)
+ {
+ ResultRelInfo *targetRelInfo;
+
+ targetRelInfo = resultRelInfo->ri_RootResultRelInfo;
+ resultRelInfo->ri_ChildToRootMap =
+ convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ RelationGetDescr(targetRelInfo->ri_RelationDesc));
+ resultRelInfo->ri_ChildToRootMapValid = true;
+ }
+
+ return resultRelInfo->ri_ChildToRootMap;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index bf65785e64..6d47344f3d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -154,12 +154,38 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
errdetail("Query has too few columns.")));
}
+/* Initializes the RETURNING projection for given result relation. */
+static void
+ExecInitReturningProjection(ResultRelInfo *resultRelInfo,
+ ModifyTableState *mtstate)
+{
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ /* should not get called twice */
+ Assert(resultRelInfo->ri_projectReturning == NULL);
+
+
+ /* ExecInitModifyTable() should've initialized these. */
+ Assert(mtstate->ps.ps_ExprContext != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(mtstate->ps.ps_ResultTupleSlot != NULL);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+
+ Assert(resultRelInfo->ri_returningList != NIL);
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(resultRelInfo->ri_returningList,
+ econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+}
+
/*
* ExecProcessReturning --- evaluate a RETURNING list
*
* resultRelInfo: current result rel
* tupleSlot: slot holding tuple actually inserted/updated/deleted
* planSlot: slot holding tuple returned by top subplan node
+ * mtstate: query's plan state
*
* Note: If tupleSlot is NULL, the FDW should have already provided econtext's
* scan tuple.
@@ -169,10 +195,18 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
static TupleTableSlot *
ExecProcessReturning(ResultRelInfo *resultRelInfo,
TupleTableSlot *tupleSlot,
- TupleTableSlot *planSlot)
+ TupleTableSlot *planSlot,
+ ModifyTableState *mtstate)
{
- ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
- ExprContext *econtext = projectReturning->pi_exprContext;
+ ProjectionInfo *projectReturning;
+ ExprContext *econtext;
+
+ /* Initialize the projection if not already done. */
+ if (resultRelInfo->ri_projectReturning == NULL)
+ ExecInitReturningProjection(resultRelInfo, mtstate);
+
+ projectReturning = resultRelInfo->ri_projectReturning;
+ econtext = projectReturning->pi_exprContext;
/* Make tuple and any needed join variables available to ExecProject */
if (tupleSlot)
@@ -371,6 +405,110 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
MemoryContextSwitchTo(oldContext);
}
+/*
+ * Initialize projection to create tuples suitable for the given result rel.
+ * INSERT queries may need a projection to filter out junk attrs in the
+ * tlist. UPDATE always needs a projection, because (1) there's always
+ * some junk attrs, and (2) we may need to merge values of not-updated
+ * columns from the old tuple into the final tuple. In UPDATE, the tuple
+ * arriving from the subplan contains only new values for the changed
+ * columns, plus row identity info in the junk attrs.
+ *
+ * This is also a convenient place to verify that the output of an INSERT or
+ * UPDATE matches the target table.
+ */
+static inline void
+ExecInitNewTupleProjection(ResultRelInfo *resultRelInfo,
+ ModifyTableState *mtstate,
+ int whichrel)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ Plan *subplan = outerPlan(node);
+ CmdType operation = mtstate->operation;
+ ListCell *l;
+
+ Assert(!resultRelInfo->ri_projectNewInfoValid);
+
+ if (operation == CMD_INSERT)
+ {
+ List *insertTargetList = NIL;
+ bool need_projection = false;
+
+ foreach(l, subplan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (!tle->resjunk)
+ insertTargetList = lappend(insertTargetList, tle);
+ else
+ need_projection = true;
+ }
+
+ /*
+ * The junk-free list must produce a tuple suitable for the result
+ * relation.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, insertTargetList);
+
+ /* We'll need a slot matching the table's format. */
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* Build ProjectionInfo if needed (it probably isn't). */
+ if (need_projection)
+ {
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildProjectionInfo(insertTargetList,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps,
+ relDesc);
+ }
+ }
+ else if (operation == CMD_UPDATE)
+ {
+ List *updateColnos;
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nrels);
+ updateColnos = (List *) list_nth(node->updateColnosLists, whichrel);
+
+ /*
+ * For UPDATE, we use the old tuple to fill up missing values in
+ * the tuple produced by the plan to get the new tuple. We need
+ * two slots, both matching the table's desired format.
+ */
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildUpdateProjection(subplan->targetlist,
+ updateColnos,
+ relDesc,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps);
+ }
+
+ resultRelInfo->ri_projectNewInfoValid = true;
+}
+
/*
* ExecGetInsertNewTuple
* This prepares a "new" tuple ready to be inserted into given result
@@ -488,6 +626,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -627,7 +768,8 @@ ExecInsert(ModifyTableState *mtstate,
* we are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate,
+ &mtstate->ps);
/*
* Check the constraints of the tuple.
@@ -641,7 +783,7 @@ ExecInsert(ModifyTableState *mtstate,
* if there's no BR trigger defined on the partition.
*/
if (resultRelationDesc->rd_rel->relispartition &&
- (resultRelInfo->ri_RootResultRelInfo == NULL ||
+ (resultRelInfo->ri_RangeTableIndex != 0 ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
ExecPartitionCheck(resultRelInfo, slot, estate, true);
@@ -822,11 +964,12 @@ ExecInsert(ModifyTableState *mtstate,
* are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
/* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- result = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ if (resultRelInfo->ri_returningList)
+ result = ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
return result;
}
@@ -882,7 +1025,8 @@ ExecBatchInsert(ModifyTableState *mtstate,
* comment in ExecInsert.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
}
if (canSetTag && numInserted > 0)
@@ -1205,7 +1349,7 @@ ldelete:;
ar_delete_trig_tcs);
/* Process RETURNING if present and if requested */
- if (processReturning && resultRelInfo->ri_projectReturning)
+ if (processReturning && resultRelInfo->ri_returningList)
{
/*
* We have to put the target tuple into a slot, which means first we
@@ -1233,7 +1377,7 @@ ldelete:;
}
}
- rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ rslot = ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
/*
* Before releasing the target tuple again, make sure rslot has a
@@ -1276,7 +1420,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;
@@ -1295,13 +1438,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
errmsg("invalid ON UPDATE specification"),
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.
- */
- if (proute == NULL)
- ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ /* Initialize tuple routing info if not already done. */
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ Relation targetRel = 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, targetRel);
+
+ /*
+ * Before a partition's tuple can be re-routed, it must first
+ * be converted to the root's format and we need a slot for
+ * storing such tuple.
+ */
+ Assert(mtstate->mt_root_tuple_slot == NULL);
+ mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ }
/*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
@@ -1364,7 +1521,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,
@@ -1434,6 +1591,9 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
ExecMaterializeSlot(slot);
/* BEFORE ROW UPDATE Triggers */
@@ -1534,7 +1694,8 @@ lreplace:;
* kind we are looking for at this point.
*/
ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
- resultRelInfo, slot, estate);
+ resultRelInfo, slot, estate,
+ &mtstate->ps);
}
/*
@@ -1547,6 +1708,13 @@ lreplace:;
*retry_slot;
bool retry;
+ /*
+ * When an UPDATE is run directly on a leaf partition, simply fail
+ * with partition constraint violation error.
+ */
+ if (resultRelInfo == mtstate->rootResultRelInfo)
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
/*
* ExecCrossPartitionUpdate will first DELETE the row from the
* partition it's currently in and then insert it back into the
@@ -1757,11 +1925,12 @@ lreplace:;
* are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
/* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- return ExecProcessReturning(resultRelInfo, slot, planSlot);
+ if (resultRelInfo->ri_returningList)
+ return ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
return NULL;
}
@@ -1955,7 +2124,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
*/
ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
existing,
- mtstate->ps.state);
+ mtstate->ps.state,
+ &mtstate->ps);
}
/* Project the new tuple version */
@@ -2244,38 +2414,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);
}
/*
@@ -2292,7 +2432,7 @@ ExecModifyTable(PlanState *pstate)
* ExecProcessReturning by IterateDirectModify, so no need to
* provide it here.
*/
- slot = ExecProcessReturning(resultRelInfo, NULL, planSlot);
+ slot = ExecProcessReturning(resultRelInfo, NULL, planSlot, node);
return slot;
}
@@ -2381,11 +2521,22 @@ ExecModifyTable(PlanState *pstate)
switch (operation)
{
case CMD_INSERT:
+ if (!resultRelInfo->ri_projectNewInfoValid)
+ ExecInitNewTupleProjection(resultRelInfo, node,
+ node->mt_lastResultIndex);
+ Assert(resultRelInfo->ri_newTupleSlot != NULL);
+
slot = ExecGetInsertNewTuple(resultRelInfo, planSlot);
slot = ExecInsert(node, resultRelInfo, slot, planSlot,
estate, node->canSetTag);
break;
case CMD_UPDATE:
+ if (!resultRelInfo->ri_projectNewInfoValid)
+ ExecInitNewTupleProjection(resultRelInfo, node,
+ node->mt_lastResultIndex);
+ Assert(resultRelInfo->ri_projectNew != NULL &&
+ resultRelInfo->ri_newTupleSlot != NULL &&
+ resultRelInfo->ri_oldTupleSlot != NULL);
/*
* Make the new tuple by combining plan's output tuple with
@@ -2408,6 +2559,7 @@ ExecModifyTable(PlanState *pstate)
oldSlot))
elog(ERROR, "failed to fetch tuple being updated");
}
+
slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot,
oldSlot);
@@ -2482,7 +2634,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)));
@@ -2542,8 +2693,37 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * Open all the result relations and initialize the ResultRelInfo structs.
- * (But root relation was initialized above, if it's part of the array.)
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ if (node->returningLists)
+ {
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
+ /*
+ * Open all the result relations and initialize the ResultRelInfo structs,
+ * initializing its fields as needed. (But root relation was initialized
+ * above, if it's part of the array.)
+ *
* We must do this before initializing the subplan, because direct-modify
* FDWs expect their ResultRelInfos to be available.
*/
@@ -2554,8 +2734,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);
@@ -2565,47 +2755,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
CheckValidResultRel(resultRelInfo, operation);
- resultRelInfo++;
- i++;
- }
-
- /*
- * Now we may initialize the subplan.
- */
- outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
-
- /*
- * Do additional per-result-relation initialization.
- */
- for (i = 0; i < nrels; i++)
- {
- 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 */
if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
@@ -2621,129 +2770,132 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int 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().
+ * Initialize any WITH CHECK OPTION constraints if needed.
*/
- 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.
- */
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- (operation == CMD_INSERT || update_tuple_routing_needed))
- 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);
-
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
+ if (node->withCheckOptionLists)
{
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists, i);
- wcoExprs = lappend(wcoExprs, wcoExpr);
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ /*
+ * ri_WithCheckOptionExprs is built the first time it needs to be
+ * used (see ExecWithCheckOptions()).
+ */
}
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
/*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
+ * Initialize RETURNING projections if needed.
*/
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists, i);
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
+ resultRelInfo->ri_returningList = rlist;
+ /*
+ * ri_projectReturning is built the first time it needs to be used
+ * (see ExecProcessReturning), unless the relation is going to be
+ * "direct modified" by its FDW.
+ */
+ if (resultRelInfo->ri_usesFdwDirectModify)
+ ExecInitReturningProjection(resultRelInfo, mtstate);
+ }
/*
- * Build a projection for each result rel.
+ * For UPDATE/DELETE, find the appropriate junk attr now, either a
+ * 'ctid' or 'wholerow' attribute depending on relkind. For foreign
+ * tables, the FDW might have created additional junk attr(s), but
+ * those are no concern of ours.
*/
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
- List *rlist = (List *) lfirst(l);
+ char relkind;
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be a
+ * wholerow attribute. We also require it to be present in
+ * UPDATE, so we can get the values of unchanged columns.
+ */
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "wholerow");
+ if (mtstate->operation == CMD_UPDATE &&
+ !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ else
+ {
+ /* Other valid target relkinds must provide wholerow */
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "wholerow");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
}
- }
- else
- {
+
/*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
+ * Determine if the FDW supports batch insert and determine the batch
+ * size (a FDW may support batching, but it may be disabled for the
+ * server/table).
+ *
+ * We only do this for INSERT, so that for UPDATE/DELETE the batch
+ * size remains set to 0.
*/
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
+ if (operation == CMD_INSERT)
+ {
+ 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;
+ Assert(resultRelInfo->ri_BatchSize >= 1);
+ }
- mtstate->ps.ps_ExprContext = NULL;
+ resultRelInfo++;
+ i++;
}
+ /*
+ * Initialize the subplan.
+ */
+ outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
+
+ /* Get the root target relation */
+ rel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ /*
+ * Build state for tuple routing if it's an INSERT. An UPDATE might need
+ * it too, but it's initialized only when it actually ends up moving
+ * tuples between partitions; see ExecCrossPartitionUpdate().
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ operation == CMD_INSERT)
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, rel);
+
/* 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
@@ -2827,151 +2979,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks);
- /*
- * Initialize projection(s) to create tuples suitable for result rel(s).
- * INSERT queries may need a projection to filter out junk attrs in the
- * tlist. UPDATE always needs a projection, because (1) there's always
- * some junk attrs, and (2) we may need to merge values of not-updated
- * columns from the old tuple into the final tuple. In UPDATE, the tuple
- * arriving from the subplan contains only new values for the changed
- * columns, plus row identity info in the junk attrs.
- *
- * If there are multiple result relations, each one needs its own
- * projection. Note multiple rels are only possible for UPDATE/DELETE, so
- * we can't be fooled by some needing a projection and some not.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
- */
- for (i = 0; i < nrels; i++)
- {
- resultRelInfo = &mtstate->resultRelInfo[i];
-
- /*
- * Prepare to generate tuples suitable for the target relation.
- */
- if (operation == CMD_INSERT)
- {
- List *insertTargetList = NIL;
- bool need_projection = false;
-
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (!tle->resjunk)
- insertTargetList = lappend(insertTargetList, tle);
- else
- need_projection = true;
- }
-
- /*
- * The junk-free list must produce a tuple suitable for the result
- * relation.
- */
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- insertTargetList);
-
- /* We'll need a slot matching the table's format. */
- resultRelInfo->ri_newTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* Build ProjectionInfo if needed (it probably isn't). */
- if (need_projection)
- {
- TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
-
- /* need an expression context to do the projection */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- resultRelInfo->ri_projectNew =
- ExecBuildProjectionInfo(insertTargetList,
- mtstate->ps.ps_ExprContext,
- resultRelInfo->ri_newTupleSlot,
- &mtstate->ps,
- relDesc);
- }
- }
- else if (operation == CMD_UPDATE)
- {
- List *updateColnos;
- TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
-
- updateColnos = (List *) list_nth(node->updateColnosLists, i);
-
- /*
- * For UPDATE, we use the old tuple to fill up missing values in
- * the tuple produced by the plan to get the new tuple. We need
- * two slots, both matching the table's desired format.
- */
- resultRelInfo->ri_oldTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
- resultRelInfo->ri_newTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* need an expression context to do the projection */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- resultRelInfo->ri_projectNew =
- ExecBuildUpdateProjection(subplan->targetlist,
- updateColnos,
- relDesc,
- mtstate->ps.ps_ExprContext,
- resultRelInfo->ri_newTupleSlot,
- &mtstate->ps);
- }
-
- /*
- * For UPDATE/DELETE, find the appropriate junk attr now, either a
- * 'ctid' or 'wholerow' attribute depending on relkind. For foreign
- * tables, the FDW might have created additional junk attr(s), but
- * those are no concern of ours.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- char relkind;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- resultRelInfo->ri_RowIdAttNo =
- ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
- if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be a
- * wholerow attribute. We also require it to be present in
- * UPDATE, so we can get the values of unchanged columns.
- */
- resultRelInfo->ri_RowIdAttNo =
- ExecFindJunkAttributeInTlist(subplan->targetlist,
- "wholerow");
- if (mtstate->operation == CMD_UPDATE &&
- !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- else
- {
- /* Other valid target relkinds must provide wholerow */
- resultRelInfo->ri_RowIdAttNo =
- ExecFindJunkAttributeInTlist(subplan->targetlist,
- "wholerow");
- if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
- }
-
/*
* If this is an inherited update/delete, there will be a junk attribute
* named "tableoid" present in the subplan's targetlist. It will be used
@@ -3026,34 +3033,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
else
mtstate->mt_resultOidHash = NULL;
- /*
- * Determine if the FDW supports batch insert and determine the batch
- * size (a FDW may support batching, but it may be disabled for the
- * server/table).
- *
- * We only do this for INSERT, so that for UPDATE/DELETE the batch
- * size remains set to 0.
- */
- if (operation == CMD_INSERT)
- {
- 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)
- resultRelInfo->ri_BatchSize =
- resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(resultRelInfo);
- else
- resultRelInfo->ri_BatchSize = 1;
-
- Assert(resultRelInfo->ri_BatchSize >= 1);
-
- resultRelInfo++;
- }
- }
-
/*
* Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
* to estate->es_auxmodifytables so that it will be run to completion by
@@ -3140,3 +3119,64 @@ ExecReScanModifyTable(ModifyTableState *node)
*/
elog(ERROR, "ExecReScanModifyTable is not implemented");
}
+
+/*
+ * ExecLookupResultRelByOid
+ * If the table with given OID is among the result relations to be
+ * updated by the given ModifyTable node, return its ResultRelInfo, NULL
+ * otherwise.
+ */
+ResultRelInfo *
+ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid,
+ bool missing_ok)
+{
+ bool found;
+
+ 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,
+ &found);
+ if (found)
+ {
+ Assert(mtlookup != NULL);
+ node->mt_lastResultOid = resultoid;
+ node->mt_lastResultIndex = mtlookup->relationIndex;
+ }
+ else if (!missing_ok)
+ elog(ERROR, "incorrect result rel OID %u", resultoid);
+ }
+ else
+ {
+ /* With few target rels, search in the pre-built OID array */
+ int ndx;
+
+ found = false;
+ for (ndx = 0; ndx < node->mt_nrels; ndx++)
+ {
+ ResultRelInfo *rInfo = node->resultRelInfo + ndx;
+
+ if (RelationGetRelid(rInfo->ri_RelationDesc) == resultoid)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ {
+ Assert(ndx < node->mt_nrels);
+ node->mt_lastResultOid = resultoid;
+ node->mt_lastResultIndex = ndx;
+ }
+ else if (!missing_ok)
+ elog(ERROR, "incorrect result rel OID %u", resultoid);
+ }
+
+ if (!found)
+ return NULL;
+
+ return node->resultRelInfo + node->mt_lastResultIndex;
+}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 354fbe4b4b..e901823b63 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1583,7 +1583,7 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo,
mtstate->ps.state = estate;
mtstate->operation = operation;
mtstate->resultRelInfo = relinfo;
- proute = ExecSetupPartitionTupleRouting(estate, mtstate, parentrel);
+ proute = ExecSetupPartitionTupleRouting(estate, parentrel);
/*
* Find the partition to which the "search tuple" belongs.
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index d30ffde7d9..694e38b7dd 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -111,7 +111,6 @@ typedef struct PartitionPruneState
} PartitionPruneState;
extern PartitionTupleRouting *ExecSetupPartitionTupleRouting(EState *estate,
- ModifyTableState *mtstate,
Relation rel);
extern ResultRelInfo *ExecFindPartition(ModifyTableState *mtstate,
ResultRelInfo *rootResultRelInfo,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 26dcc4485e..7c6ee53d4f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -211,7 +211,8 @@ extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo,
extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
- TupleTableSlot *slot, EState *estate);
+ TupleTableSlot *slot, EState *estate,
+ PlanState *parent);
extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti, bool missing_ok);
extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
@@ -596,6 +597,7 @@ extern int ExecCleanTargetListLength(List *targetlist);
extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
+extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
@@ -645,9 +647,11 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
-/* needed by trigger.c */
+/* prototypes from nodeModifyTable.c */
extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot);
+extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid,
+ bool missing_ok);
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 52d1fa018b..a0b29745bb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -431,6 +431,8 @@ typedef struct ResultRelInfo
TupleTableSlot *ri_newTupleSlot;
/* Slot to hold the old tuple being updated */
TupleTableSlot *ri_oldTupleSlot;
+ /* Have the projection and the slots above been initialized? */
+ bool ri_projectNewInfoValid;
/* triggers to be fired, if any */
TriggerDesc *ri_TrigDesc;
@@ -516,6 +518,8 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ /* has the map been initialized? */
+ bool ri_ChildToRootMapValid;
/* for use by copyfrom.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 1c703c351f..06f44287bc 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2492,7 +2492,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint "
DETAIL: Failing row contains (10, 1, 15).
UPDATE errtst_parent SET data = data + 10 WHERE partid = 20;
ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
-DETAIL: Failing row contains (15, 1, 20).
+DETAIL: Failing row contains (20, 1, 15).
-- direct leaf partition update, without partition id violation
BEGIN;
UPDATE errtst_child_fastdef SET partid = 1 WHERE partid = 0;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 89f3d5da46..6372e0ed6a 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -685,7 +685,7 @@ DETAIL: Failing row contains (a, b, c) = (aaa, null, null).
-- simple update.
UPDATE errtst SET b = NULL;
ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint
-DETAIL: Failing row contains (b) = (null).
+DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc).
-- partitioning key is updated, doesn't move the row.
UPDATE errtst SET a = 'aaa', b = NULL;
ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index cdff914b93..dd309283be 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1892,28 +1892,22 @@ UPDATE rw_view1 SET a = a + 5; -- should fail
ERROR: new row violates check option for view "rw_view1"
DETAIL: Failing row contains (15).
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
- QUERY PLAN
----------------------------------------------------------
+ QUERY PLAN
+----------------------
Insert on base_tbl b
-> Result
- SubPlan 1
- -> Index Only Scan using ref_tbl_pkey on ref_tbl r
- Index Cond: (a = b.a)
-(5 rows)
+(2 rows)
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
- QUERY PLAN
------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------
Update on base_tbl b
-> Hash Join
Hash Cond: (b.a = r.a)
-> Seq Scan on base_tbl b
-> Hash
-> Seq Scan on ref_tbl r
- SubPlan 1
- -> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
- Index Cond: (a = b.a)
-(9 rows)
+(6 rows)
DROP TABLE base_tbl, ref_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index dc34ac67b3..ad91e5aedb 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -342,8 +342,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15).
-- fail, no partition key update, so no attempt to move tuple,
-- but "a = 'a'" violates partition constraint enforced by root partition)
UPDATE part_b_10_b_20 set a = 'a';
-ERROR: new row for relation "part_c_1_100" violates partition constraint
-DETAIL: Failing row contains (null, 1, 96, 12, a).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (null, 96, a, 12, 1).
-- ok, partition key update, no constraint violation
UPDATE range_parted set d = d - 10 WHERE d > 10;
-- ok, no partition key update, no constraint violation
@@ -373,8 +373,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
-- fail, row movement happens only within the partition subtree.
UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR: new row for relation "part_d_1_15" violates partition constraint
-DETAIL: Failing row contains (2, 117, 2, b, 7).
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (2, 117, b, 7, 2).
-- ok, row movement, with subset of rows moved into different partition.
UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
a | ?column?
@@ -815,8 +815,8 @@ INSERT into sub_parted VALUES (1,2,10);
-- Test partition constraint violation when intermediate ancestor is used and
-- constraint is inherited from upper root.
UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR: new row for relation "sub_part2" violates partition constraint
-DETAIL: Failing row contains (2, 10, 2).
+ERROR: new row for relation "sub_parted" violates partition constraint
+DETAIL: Failing row contains (2, 2, 10).
-- Test update-partition-key, where the unpruned partitions do not have their
-- partition keys updated.
SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
Amit Langote <amitlangote09@gmail.com> writes:
On Mon, Apr 5, 2021 at 1:43 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
OK. Do you want to pull out the bits of the patch that we can still
do without postponing BeginDirectModify?
I ended up with the attached, whereby ExecInitResultRelation() is now
performed for all relations before calling ExecInitNode() on the
subplan. As mentioned, I moved other per-result-rel initializations
into the same loop that does ExecInitResultRelation(), while moving
code related to some initializations into initialize-on-first-access
accessor functions for the concerned fields. I chose to do that for
ri_WIthCheckOptionExprs, ri_projectReturning, and ri_projectNew.
I pushed the parts of this that I thought were safe and productive.
The business about moving the subplan tree initialization to after
calling FDWs' BeginForeignModify functions seems to me to be a
nonstarter. Existing FDWs are going to expect their scan initializations
to have been done first. I'm surprised that postgres_fdw seemed to
need only a one-line fix; it could have been far worse. The amount of
trouble that could cause is absolutely not worth it to remove one loop
over the result relations.
I also could not get excited about postponing initialization of RETURNING
or WITH CHECK OPTIONS expressions. I grant that that can be helpful
when those features are used, but I doubt that RETURNING is used that
heavily, and WITH CHECK OPTIONS is surely next door to nonexistent
in performance-critical queries. If the feature isn't used, the cost
of the existing code is about zero. So I couldn't see that it was worth
the amount of code thrashing and risk of new bugs involved. The bit you
noted about EXPLAIN missing a subplan is pretty scary in this connection;
I'm not at all sure that that's just cosmetic.
(Having said that, I'm wondering if there are bugs in these cases for
cross-partition updates that target a previously-not-used partition.
So we might have things to fix anyway.)
Anyway, looking at the test case you posted at the very top of this
thread, I was getting this with HEAD on Friday:
nparts TPS
0 12152
10 8672
100 2753
1000 314
and after the two patches I just pushed, it looks like:
0 12105
10 9928
100 5433
1000 938
So while there's certainly work left to do, that's not bad for
some low-hanging-fruit grabbing.
regards, tom lane
Hi,
On 2021-04-06 19:24:11 -0400, Tom Lane wrote:
I also could not get excited about postponing initialization of RETURNING
or WITH CHECK OPTIONS expressions. I grant that that can be helpful
when those features are used, but I doubt that RETURNING is used that
heavily, and WITH CHECK OPTIONS is surely next door to nonexistent
in performance-critical queries.
FWIW, there's a number of ORMs etc that use it on every insert (there's
not really a better way to get the serial when you also want to do
pipelining).
nparts TPS
0 12152
10 8672
100 2753
1000 314and after the two patches I just pushed, it looks like:
0 12105
10 9928
100 5433
1000 938So while there's certainly work left to do, that's not bad for
some low-hanging-fruit grabbing.
Nice. 3x at the upper end is pretty good.
Greetings,
Andres Freund
On Wed, Apr 7, 2021 at 8:24 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Langote <amitlangote09@gmail.com> writes:
On Mon, Apr 5, 2021 at 1:43 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
OK. Do you want to pull out the bits of the patch that we can still
do without postponing BeginDirectModify?I ended up with the attached, whereby ExecInitResultRelation() is now
performed for all relations before calling ExecInitNode() on the
subplan. As mentioned, I moved other per-result-rel initializations
into the same loop that does ExecInitResultRelation(), while moving
code related to some initializations into initialize-on-first-access
accessor functions for the concerned fields. I chose to do that for
ri_WIthCheckOptionExprs, ri_projectReturning, and ri_projectNew.I pushed the parts of this that I thought were safe and productive.
Thank you.
+/*
+ * ExecInitInsertProjection
+ * Do one-time initialization of projection data for INSERT tuples.
+ *
+ * INSERT queries may need a projection to filter out junk attrs in the tlist.
+ *
+ * This is "one-time" for any given result rel, but we might touch
+ * more than one result rel in the course of a partitioned INSERT.
I don't think we need this last bit for INSERT, because the result
rels for leaf partitions will never have to go through
ExecInitInsertProjection(). Leaf partitions are never directly fed
tuples that ExecModifyTable() extracts out of the subplan, because
those tuples will have gone through the root target table's projection
before being passed to tuple routing. So, if INSERTs will ever need a
projection, only the partitioned table being inserted into will need
to have one built for.
Also, I think we should update the commentary around ri_projectNew a
bit to make it clear that noplace beside ExecGet{Insert|Update}Tuple
should be touching it and the associated slots.
+ * This is "one-time" for any given result rel, but we might touch more than
+ * one result rel in the course of a partitioned UPDATE, and each one needs
+ * its own projection due to possible column order variation.
Minor quibble, but should we write it as "...in the course of an
inherited UPDATE"?
Attached patch contains these changes.
The business about moving the subplan tree initialization to after
calling FDWs' BeginForeignModify functions seems to me to be a
nonstarter. Existing FDWs are going to expect their scan initializations
to have been done first. I'm surprised that postgres_fdw seemed to
need only a one-line fix; it could have been far worse. The amount of
trouble that could cause is absolutely not worth it to remove one loop
over the result relations.
Okay, that sounds fair. After all, we write this about 'mtstate' in
the description of BeginForeignModify(), which I had failed to notice:
"mtstate is the overall state of the ModifyTable plan node being
executed; global data about the plan and execution state is available
via this structure."
I also could not get excited about postponing initialization of RETURNING
or WITH CHECK OPTIONS expressions. I grant that that can be helpful
when those features are used, but I doubt that RETURNING is used that
heavily, and WITH CHECK OPTIONS is surely next door to nonexistent
in performance-critical queries. If the feature isn't used, the cost
of the existing code is about zero. So I couldn't see that it was worth
the amount of code thrashing and risk of new bugs involved.
Okay.
The bit you
noted about EXPLAIN missing a subplan is pretty scary in this connection;
I'm not at all sure that that's just cosmetic.
Yeah, this and...
(Having said that, I'm wondering if there are bugs in these cases for
cross-partition updates that target a previously-not-used partition.
So we might have things to fix anyway.)
...this would need to be looked at a bit more closely, which I'll try
to do sometime later this week.
Anyway, looking at the test case you posted at the very top of this
thread, I was getting this with HEAD on Friday:nparts TPS
0 12152
10 8672
100 2753
1000 314and after the two patches I just pushed, it looks like:
0 12105
10 9928
100 5433
1000 938So while there's certainly work left to do, that's not bad for
some low-hanging-fruit grabbing.
Yes, certainly.
I reran my usual benchmark and got the following numbers, this time
comparing v13.2 against the latest HEAD:
nparts 10cols 20cols 40cols
v13.2
64 3231 2747 2217
128 1528 1269 1121
256 709 652 491
1024 96 78 67
v14dev HEAD
64 14835 14360 14563
128 9469 9601 9490
256 5523 5383 5268
1024 1482 1415 1366
Clearly, we've made some very good progress here. Thanks.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
projectNew-comment-fixes.patchapplication/octet-stream; name=projectNew-comment-fixes.patchDownload
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6a16752c73..c5a2a9a054 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -377,9 +377,6 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
*
* INSERT queries may need a projection to filter out junk attrs in the tlist.
*
- * This is "one-time" for any given result rel, but we might touch
- * more than one result rel in the course of a partitioned INSERT.
- *
* This is also a convenient place to verify that the
* output of an INSERT matches the target table.
*/
@@ -447,7 +444,7 @@ ExecInitInsertProjection(ModifyTableState *mtstate,
* identity info in the junk attrs.
*
* This is "one-time" for any given result rel, but we might touch more than
- * one result rel in the course of a partitioned UPDATE, and each one needs
+ * one result rel in the course of an inherited UPDATE, and each one needs
* its own projection due to possible column order variation.
*
* This is also a convenient place to verify that the output of an UPDATE
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7ae21c023..6c21ebb17f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -425,11 +425,15 @@ typedef struct ResultRelInfo
*/
AttrNumber ri_RowIdAttNo;
- /* Projection to generate new tuple in an INSERT/UPDATE */
+ /*
+ * Projection to generate new tuple in an INSERT/UPDATE and slot to hold
+ * it. For UPDATE, oldTupleSlot holds the old tuple being updated.
+ *
+ * NB: These fields are not to be touched unless you are either
+ * ExecGetNewInsertTuple() or ExecGetNewUpdateTuple().
+ */
ProjectionInfo *ri_projectNew;
- /* Slot to hold that tuple */
TupleTableSlot *ri_newTupleSlot;
- /* Slot to hold the old tuple being updated */
TupleTableSlot *ri_oldTupleSlot;
/* Have the projection and the slots above been initialized? */
bool ri_projectNewInfoValid;
Amit Langote <amitlangote09@gmail.com> writes:
Also, I think we should update the commentary around ri_projectNew a
bit to make it clear that noplace beside ExecGet{Insert|Update}Tuple
should be touching it and the associated slots.
Hm. I pushed your comment fixes in nodeModifyTable.c, but not this
change, because it seemed to be more verbose and not really an
improvement. Why are these fields any more hands-off than any others?
Besides which, there certainly is other code touching ri_oldTupleSlot.
Anyway, I've marked the CF entry closed, because I think this is about
as far as we can get for v14. I'm not averse to revisiting the
RETURNING and WITH CHECK OPTIONS issues later, but it looks to me like
that needs more study.
I reran my usual benchmark and got the following numbers, this time
comparing v13.2 against the latest HEAD:
nparts 10cols 20cols 40cols
v13.2
64 3231 2747 2217
128 1528 1269 1121
256 709 652 491
1024 96 78 67
v14dev HEAD
64 14835 14360 14563
128 9469 9601 9490
256 5523 5383 5268
1024 1482 1415 1366
Clearly, we've made some very good progress here. Thanks.
Indeed, that's a pretty impressive comparison.
regards, tom lane
On Wed, Apr 7, 2021 at 12:34 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
v13.2
64 3231 2747 2217
128 1528 1269 1121
256 709 652 491
1024 96 78 67v14dev HEAD
64 14835 14360 14563
128 9469 9601 9490
256 5523 5383 5268
1024 1482 1415 1366Clearly, we've made some very good progress here. Thanks.
Indeed, that's a pretty impressive comparison.
+1. That looks like a big improvement.
In a vacuum, you'd hope that partitioning a table would make things
faster rather than slower, when only one partition is implicated. Or
at least that the speed would stay about the same. And, while this is
a lot better, we're clearly not there yet. So I hope that, in future
releases, we can continue to find ways to whittle down the overhead.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
On Wed, Apr 7, 2021 at 12:34 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Indeed, that's a pretty impressive comparison.
+1. That looks like a big improvement.
In a vacuum, you'd hope that partitioning a table would make things
faster rather than slower, when only one partition is implicated. Or
at least that the speed would stay about the same. And, while this is
a lot better, we're clearly not there yet. So I hope that, in future
releases, we can continue to find ways to whittle down the overhead.
Note that this test case includes plan_cache_mode = force_generic_plan,
so it's deliberately kneecapping our ability to tell that "only one
partition is implicated". I think things would often be better in
production cases. No argument that there's not work left to do, though.
regards, tom lane
On Thu, Apr 8, 2021 at 1:34 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Langote <amitlangote09@gmail.com> writes:
Also, I think we should update the commentary around ri_projectNew a
bit to make it clear that noplace beside ExecGet{Insert|Update}Tuple
should be touching it and the associated slots.Hm. I pushed your comment fixes in nodeModifyTable.c, but not this
change, because it seemed to be more verbose and not really an
improvement. Why are these fields any more hands-off than any others?
Besides which, there certainly is other code touching ri_oldTupleSlot.
Oops, that's right.
Anyway, I've marked the CF entry closed, because I think this is about
as far as we can get for v14. I'm not averse to revisiting the
RETURNING and WITH CHECK OPTIONS issues later, but it looks to me like
that needs more study.
Sure, I will look into that.
--
Amit Langote
EDB: http://www.enterprisedb.com
On Thu, Apr 8, 2021 at 3:02 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Wed, Apr 7, 2021 at 12:34 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Indeed, that's a pretty impressive comparison.
+1. That looks like a big improvement.
In a vacuum, you'd hope that partitioning a table would make things
faster rather than slower, when only one partition is implicated. Or
at least that the speed would stay about the same. And, while this is
a lot better, we're clearly not there yet. So I hope that, in future
releases, we can continue to find ways to whittle down the overhead.Note that this test case includes plan_cache_mode = force_generic_plan,
so it's deliberately kneecapping our ability to tell that "only one
partition is implicated".
For the record, here are the numbers for plan_cache_mode = auto.
(Actually, plancache.c always goes with custom planning for
partitioned tables.)
v13.2
nparts 10cols 20cols 40cols
64 13391 12140 10958
128 13436 12297 10643
256 12564 12294 10355
1024 11450 10600 9020
v14dev HEAD
64 14925 14648 13361
128 14379 14333 13138
256 14478 14246 13316
1024 12744 12621 11579
There's 10-20% improvement in this case too for various partition
counts, which really has more to do with 86dc90056 than the work done
here.
--
Amit Langote
EDB: http://www.enterprisedb.com
On Thu, 8 Apr 2021 at 15:32, Amit Langote <amitlangote09@gmail.com> wrote:
There's 10-20% improvement in this case too for various partition
counts, which really has more to do with 86dc90056 than the work done
here.
I'm not sure of the exact query you're running, but I imagine the
reason that it wasn't that slow with custom plans was down to
428b260f87.
So the remaining slowness for the generic plan case with large numbers
of partitions in the plan vs custom plans plan-time pruning is a)
locking run-time pruned partitions; and; b) permission checks during
executor startup?
Aside from the WCO and RETURNING stuff you mentioned, I mean.
David
On Thu, Apr 8, 2021 at 1:54 PM David Rowley <dgrowleyml@gmail.com> wrote:
On Thu, 8 Apr 2021 at 15:32, Amit Langote <amitlangote09@gmail.com> wrote:
There's 10-20% improvement in this case too for various partition
counts, which really has more to do with 86dc90056 than the work done
here.I'm not sure of the exact query you're running,
The query is basically this:
\set a random(1, 1000000)
update test_table set b = :a where a = :a;
but I imagine the
reason that it wasn't that slow with custom plans was down to
428b260f87.
Right, 428b260f87 is certainly why we are seeing numbers this big at
all. However, I was saying that 86dc90056 is what makes v14 HEAD run
about 10-20% faster than *v13.2* in this benchmark. Note that
inheritance_planner() in v13, which, although not as bad as it used to
be in v11, is still more expensive than a single grouping_planner()
call for a given query that we now get thanks to 86dc90056.
So the remaining slowness for the generic plan case with large numbers
of partitions in the plan vs custom plans plan-time pruning is a)
locking run-time pruned partitions; and; b) permission checks during
executor startup?
Actually, we didn't move ahead with making the ResulRelInfos
themselves lazily as I had proposed in the original patch, so
ExecInitModifyTable() still builds ResultRelInfos for all partitions.
Although we did move initializations of some fields of it out of
ExecInitModifyTable() --- commits a1115fa0, c5b7ba4e, saving a decent
amount of time spent there. We need to study closely whether
initializing foreign partition's updates (direct or otherwise) lazily
doesn't produce wrong semantics before we can do that and we need the
ResultRelInfos to pass to those APIs.
--
Amit Langote
EDB: http://www.enterprisedb.com
On Wed, Apr 7, 2021 at 5:18 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Wed, Apr 7, 2021 at 8:24 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
I also could not get excited about postponing initialization of RETURNING
or WITH CHECK OPTIONS expressions. I grant that that can be helpful
when those features are used, but I doubt that RETURNING is used that
heavily, and WITH CHECK OPTIONS is surely next door to nonexistent
in performance-critical queries. If the feature isn't used, the cost
of the existing code is about zero. So I couldn't see that it was worth
the amount of code thrashing and risk of new bugs involved.Okay.
The bit you
noted about EXPLAIN missing a subplan is pretty scary in this connection;
I'm not at all sure that that's just cosmetic.Yeah, this and...
I looked into this and can't see why this isn't just cosmetic as far
as ModifyTable is concerned.
"EXPLAIN missing a subplan" here just means that
ModifyTableState.PlanState.subPlan is not set. Besides ExplainNode(),
only ExecReScan() looks at PlanState.subPlan, and that does not seem
relevant to ModifyTable, because it doesn't support rescanning.
I don't see any such problems with creating RETURNING projections
on-demand either.
(Having said that, I'm wondering if there are bugs in these cases for
cross-partition updates that target a previously-not-used partition.
So we might have things to fix anyway.)...this would need to be looked at a bit more closely, which I'll try
to do sometime later this week.
Given the above, I can't think of any undiscovered problems related to
WCO and RETURNING expression states in the cases where cross-partition
updates target partitions that need to be initialized by
ExecInitPartitionInfo(). Here is the result for the test case in
updatable_views.sql modified to use partitioning and cross-partition
updates:
CREATE TABLE base_tbl (a int) partition by range (a);
CREATE TABLE base_tbl1 PARTITION OF base_tbl FOR VALUES FROM (1) TO (6);
CREATE TABLE base_tbl2 PARTITION OF base_tbl FOR VALUES FROM (6) TO (11);
CREATE TABLE base_tbl3 PARTITION OF base_tbl FOR VALUES FROM (11) TO (15);
CREATE TABLE ref_tbl (a int PRIMARY KEY);
INSERT INTO ref_tbl SELECT * FROM generate_series(1,10);
CREATE VIEW rw_view1 AS
SELECT * FROM base_tbl b
WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a)
WITH CHECK OPTION;
INSERT INTO rw_view1 VALUES (1);
INSERT 0 1
INSERT INTO rw_view1 VALUES (11);
ERROR: new row violates check option for view "rw_view1"
DETAIL: Failing row contains (11).
-- Both are cross-partition updates where the target relation is
-- lazily initialized in ExecInitPartitionInfo(), along with the WCO
-- qual ExprState
UPDATE rw_view1 SET a = a + 5 WHERE a = 1;
UPDATE 1
UPDATE rw_view1 SET a = a + 5 WHERE a = 6;
ERROR: new row violates check option for view "rw_view1"
DETAIL: Failing row contains (11).
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
QUERY PLAN
----------------------
Insert on base_tbl b
-> Result
(2 rows)
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5 WHERE a = 1;
QUERY PLAN
--------------------------------------------------------
Update on base_tbl b
Update on base_tbl1 b_1
-> Nested Loop
-> Index Scan using ref_tbl_pkey on ref_tbl r
Index Cond: (a = 1)
-> Seq Scan on base_tbl1 b_1
Filter: (a = 1)
(7 rows)
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5 WHERE a = 6;
QUERY PLAN
--------------------------------------------------------
Update on base_tbl b
Update on base_tbl2 b_1
-> Nested Loop
-> Index Scan using ref_tbl_pkey on ref_tbl r
Index Cond: (a = 6)
-> Seq Scan on base_tbl2 b_1
Filter: (a = 6)
(7 rows)
Patch attached. I tested the performance benefit of doing this by
modifying the update query used in earlier benchmarks to have a
RETURNING * clause, getting the following TPS numbers:
-Mprepared (plan_cache_mode=force_generic_plan)
nparts 10cols 20cols 40cols
HEAD
64 10909 9067 7171
128 6903 5624 4161
256 3748 3056 2219
1024 953 738 427
Patched
64 13817 13395 12754
128 9271 9102 8279
256 5345 5207 5083
1024 1463 1443 1389
Also, I don't see much impact of checking if (node->returningLists) in
the per-result-rel initialization loop in the common cases where
there's no RETURNING.
--
Amit Langote
EDB: http://www.enterprisedb.com
Attachments:
0001-Initialize-WITH-CHECK-OPTIONS-and-RETURNING-expressi.patchapplication/octet-stream; name=0001-Initialize-WITH-CHECK-OPTIONS-and-RETURNING-expressi.patchDownload
From c6456053de33fd43f9389ab056a0e67670be2f43 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Fri, 9 Apr 2021 15:23:56 +0900
Subject: [PATCH] Initialize WITH CHECK OPTIONS and RETURNING expressions on
demand
---
src/backend/executor/execMain.c | 19 +-
src/backend/executor/execPartition.c | 33 +--
src/backend/executor/nodeModifyTable.c | 210 ++++++++++--------
src/include/executor/executor.h | 3 +-
src/test/regress/expected/updatable_views.out | 18 +-
5 files changed, 152 insertions(+), 131 deletions(-)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b2e2df8773..fca60736f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1950,7 +1950,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
*/
void
ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
- TupleTableSlot *slot, EState *estate)
+ TupleTableSlot *slot, EState *estate,
+ PlanState *parent)
{
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
@@ -1958,6 +1959,22 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
ListCell *l1,
*l2;
+ /* Initialize the quals if not already done. */
+ if (resultRelInfo->ri_WithCheckOptionExprs == NIL)
+ {
+ List *wcoExprs = NIL;
+
+ foreach(l1, resultRelInfo->ri_WithCheckOptions)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(l1);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ parent);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
/*
* We will use the EState's per-tuple context for evaluating constraint
* expressions (creating it if it's not already there).
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 99780ebb96..7dccc55006 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -543,8 +543,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
if (node && node->withCheckOptionLists != NIL)
{
List *wcoList;
- List *wcoExprs = NIL;
- ListCell *ll;
/*
* In the case of INSERT on a partitioned table, there is only one
@@ -582,18 +580,11 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
RelationGetForm(partrel)->reltype,
&found_whole_row);
/* We ignore the value of found_whole_row. */
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = castNode(WithCheckOption, lfirst(ll));
- ExprState *wcoExpr = ExecInitQual(castNode(List, wco->qual),
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
leaf_part_rri->ri_WithCheckOptions = wcoList;
- leaf_part_rri->ri_WithCheckOptionExprs = wcoExprs;
+ /*
+ * ri_WithCheckOptionExprs is built the first time it needs to be
+ * used (see ExecWithCheckOptions()).
+ */
}
/*
@@ -605,8 +596,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->returningLists != NIL)
{
- TupleTableSlot *slot;
- ExprContext *econtext;
List *returningList;
/* See the comment above for WCO lists. */
@@ -641,20 +630,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/* We ignore the value of found_whole_row. */
leaf_part_rri->ri_returningList = returningList;
-
/*
- * Initialize the projection itself.
- *
- * Use the slot and the expression context that would have been set up
- * in ExecInitModifyTable() for projection's output.
+ * ri_projectReturning is built the first time it needs to be
+ * used (see ExecProcessReturning()).
*/
- Assert(mtstate->ps.ps_ResultTupleSlot != NULL);
- slot = mtstate->ps.ps_ResultTupleSlot;
- Assert(mtstate->ps.ps_ExprContext != NULL);
- econtext = mtstate->ps.ps_ExprContext;
- leaf_part_rri->ri_projectReturning =
- ExecBuildProjectionInfo(returningList, econtext, slot,
- &mtstate->ps, RelationGetDescr(partrel));
}
/* Set up information needed for routing tuples to the partition. */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c5a2a9a054..dba9f684a5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -154,12 +154,37 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
errdetail("Query has too few columns.")));
}
+/* Initializes the RETURNING projection for given result relation. */
+static void
+ExecInitReturningProjection(ResultRelInfo *resultRelInfo,
+ ModifyTableState *mtstate)
+{
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ /* should not get called twice */
+ Assert(resultRelInfo->ri_projectReturning == NULL);
+
+ /* ExecInitModifyTable() should've initialized these. */
+ Assert(mtstate->ps.ps_ExprContext != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(mtstate->ps.ps_ResultTupleSlot != NULL);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+
+ Assert(resultRelInfo->ri_returningList != NIL);
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(resultRelInfo->ri_returningList,
+ econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+}
+
/*
* ExecProcessReturning --- evaluate a RETURNING list
*
* resultRelInfo: current result rel
* tupleSlot: slot holding tuple actually inserted/updated/deleted
* planSlot: slot holding tuple returned by top subplan node
+ * mtstate: query's plan state
*
* Note: If tupleSlot is NULL, the FDW should have already provided econtext's
* scan tuple.
@@ -169,10 +194,18 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
static TupleTableSlot *
ExecProcessReturning(ResultRelInfo *resultRelInfo,
TupleTableSlot *tupleSlot,
- TupleTableSlot *planSlot)
+ TupleTableSlot *planSlot,
+ ModifyTableState *mtstate)
{
- ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
- ExprContext *econtext = projectReturning->pi_exprContext;
+ ProjectionInfo *projectReturning;
+ ExprContext *econtext;
+
+ /* Initialize the projection if not already done. */
+ if (resultRelInfo->ri_projectReturning == NULL)
+ ExecInitReturningProjection(resultRelInfo, mtstate);
+
+ projectReturning = resultRelInfo->ri_projectReturning;
+ econtext = projectReturning->pi_exprContext;
/* Make tuple and any needed join variables available to ExecProject */
if (tupleSlot)
@@ -767,7 +800,8 @@ ExecInsert(ModifyTableState *mtstate,
* we are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate,
+ &mtstate->ps);
/*
* Check the constraints of the tuple.
@@ -962,11 +996,12 @@ ExecInsert(ModifyTableState *mtstate,
* are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
/* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- result = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ if (resultRelInfo->ri_returningList)
+ result = ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
return result;
}
@@ -1022,7 +1057,8 @@ ExecBatchInsert(ModifyTableState *mtstate,
* comment in ExecInsert.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
}
if (canSetTag && numInserted > 0)
@@ -1345,7 +1381,7 @@ ldelete:;
ar_delete_trig_tcs);
/* Process RETURNING if present and if requested */
- if (processReturning && resultRelInfo->ri_projectReturning)
+ if (processReturning && resultRelInfo->ri_returningList)
{
/*
* We have to put the target tuple into a slot, which means first we
@@ -1373,7 +1409,7 @@ ldelete:;
}
}
- rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ rslot = ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
/*
* Before releasing the target tuple again, make sure rslot has a
@@ -1707,7 +1743,8 @@ lreplace:;
* kind we are looking for at this point.
*/
ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
- resultRelInfo, slot, estate);
+ resultRelInfo, slot, estate,
+ &mtstate->ps);
}
/*
@@ -1934,11 +1971,12 @@ lreplace:;
* are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
/* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- return ExecProcessReturning(resultRelInfo, slot, planSlot);
+ if (resultRelInfo->ri_returningList)
+ return ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
return NULL;
}
@@ -2132,7 +2170,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
*/
ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
existing,
- mtstate->ps.state);
+ mtstate->ps.state,
+ &mtstate->ps);
}
/* Project the new tuple version */
@@ -2439,7 +2478,7 @@ ExecModifyTable(PlanState *pstate)
* ExecProcessReturning by IterateDirectModify, so no need to
* provide it here.
*/
- slot = ExecProcessReturning(resultRelInfo, NULL, planSlot);
+ slot = ExecProcessReturning(resultRelInfo, NULL, planSlot, node);
return slot;
}
@@ -2748,6 +2787,33 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
ExecSetupTransitionCaptureState(mtstate, estate);
+ /*
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ if (node->returningLists)
+ {
+ mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+ /* Set up a slot for the output of the RETURNING projection(s) */
+ ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+ /* Need an econtext too */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+ else
+ {
+ /*
+ * We still must construct a dummy result tuple type, because InitPlan
+ * expects one (maybe should change that?).
+ */
+ mtstate->ps.plan->targetlist = NIL;
+ ExecInitResultTypeTL(&mtstate->ps);
+
+ mtstate->ps.ps_ExprContext = NULL;
+ }
+
/*
* Open all the result relations and initialize the ResultRelInfo structs.
* (But root relation was initialized above, if it's part of the array.)
@@ -2856,6 +2922,44 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
elog(ERROR, "could not find junk wholerow column");
}
}
+
+ /*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ if (node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists, i);
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ /*
+ * ri_WithCheckOptionExprs is built the first time it needs to be
+ * used (see ExecWithCheckOptions()).
+ */
+ }
+
+ /*
+ * Initialize RETURNING projections if needed.
+ */
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists, i);
+
+ resultRelInfo->ri_returningList = rlist;
+
+ /*
+ * ri_projectReturning is built the first time it needs to be used
+ * (see ExecProcessReturning()), unless the relation is to be
+ * "directly modified", in that case, IterateDirectModify()
+ * expects ri_projectReturning to be valid. Because the child
+ * relations (their ResultRelInfos) that are directly modified are
+ * accessed from their corresponding ForeignScanState nodes,
+ * they can get switched without the control returning to the top-
+ * level ModifyTable, so there's no better way than initializing
+ * the projection for all such result relations here.
+ */
+ if (resultRelInfo->ri_usesFdwDirectModify)
+ ExecInitReturningProjection(resultRelInfo, mtstate);
+ }
}
/*
@@ -2883,80 +2987,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_partition_tuple_routing =
ExecSetupPartitionTupleRouting(estate, rel);
- /*
- * Initialize any WITH CHECK OPTION constraints if needed.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- }
-
- /*
- * Initialize RETURNING projections if needed.
- */
- if (node->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
-
- /*
- * Initialize result tuple slot and assign its rowtype using the first
- * RETURNING list. We assume the rest will look the same.
- */
- mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
- slot = mtstate->ps.ps_ResultTupleSlot;
-
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
-
- /*
- * Build a projection for each result rel.
- */
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
- {
- List *rlist = (List *) lfirst(l);
-
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
- else
- {
- /*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
- */
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
-
- mtstate->ps.ps_ExprContext = NULL;
- }
-
/* Set the list of arbiter indexes if needed for ON CONFLICT */
resultRelInfo = mtstate->resultRelInfo;
if (node->onConflictAction != ONCONFLICT_NONE)
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 6eae134c08..622cb26c88 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -211,7 +211,8 @@ extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo,
extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
- TupleTableSlot *slot, EState *estate);
+ TupleTableSlot *slot, EState *estate,
+ PlanState *parent);
extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti, bool missing_ok);
extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index cdff914b93..dd309283be 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1892,28 +1892,22 @@ UPDATE rw_view1 SET a = a + 5; -- should fail
ERROR: new row violates check option for view "rw_view1"
DETAIL: Failing row contains (15).
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
- QUERY PLAN
----------------------------------------------------------
+ QUERY PLAN
+----------------------
Insert on base_tbl b
-> Result
- SubPlan 1
- -> Index Only Scan using ref_tbl_pkey on ref_tbl r
- Index Cond: (a = b.a)
-(5 rows)
+(2 rows)
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
- QUERY PLAN
------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------
Update on base_tbl b
-> Hash Join
Hash Cond: (b.a = r.a)
-> Seq Scan on base_tbl b
-> Hash
-> Seq Scan on ref_tbl r
- SubPlan 1
- -> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
- Index Cond: (a = b.a)
-(9 rows)
+(6 rows)
DROP TABLE base_tbl, ref_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
--
2.24.1