ri_usesMultiInsert.patch
application/octet-stream
Filename: ri_usesMultiInsert.patch
Type: application/octet-stream
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/postgres_fdw.c | 0 | 3 |
| src/backend/commands/copy.c | 60 | 119 |
| src/backend/commands/tablecmds.c | 1 | 0 |
| src/backend/executor/execMain.c | 53 | 1 |
| src/backend/executor/execPartition.c | 4 | 10 |
| src/backend/replication/logical/worker.c | 1 | 1 |
| src/include/executor/executor.h | 1 | 0 |
| src/include/nodes/execnodes.h | 7 | 8 |
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index a314821..19cf119 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1859,9 +1859,6 @@ postgresExecForeignInsert(EState *estate,
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
TupleTableSlot *rslot;
- Assert(!resultRelInfo->ri_usesBulkModify ||
- resultRelInfo->ri_FdwRoutine->BeginForeignCopyIn == NULL);
-
/*
* If the fmstate has aux_fmstate set, use the aux_fmstate (see
* postgresBeginForeignInsert())
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fd1f1d6..43cd8c0 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -86,16 +86,6 @@ typedef enum EolType
} EolType;
/*
- * Represents the heap insert method to be used during COPY FROM.
- */
-typedef enum CopyInsertMethod
-{
- CIM_SINGLE, /* use table_tuple_insert or fdw routine */
- CIM_MULTI, /* always use table_multi_insert */
- CIM_MULTI_CONDITIONAL /* use table_multi_insert only if valid */
-} CopyInsertMethod;
-
-/*
* This struct contains all the state variables used throughout a COPY
* operation. For simplicity, we use the same struct for all variants of COPY,
* even though some fields are used in only some cases.
@@ -2762,12 +2752,11 @@ CopyFrom(CopyState cstate)
CommandId mycid = GetCurrentCommandId(true);
int ti_options = 0; /* start with default options for insert */
BulkInsertState bistate = NULL;
- CopyInsertMethod insertMethod;
+ bool use_multi_insert;
CopyMultiInsertInfo multiInsertInfo = {0}; /* pacify compiler */
uint64 processed = 0;
bool has_before_insert_row_trig;
bool has_instead_insert_row_trig;
- bool leafpart_use_multi_insert = false;
Assert(cstate->rel);
@@ -2868,6 +2857,52 @@ CopyFrom(CopyState cstate)
}
/*
+ * It's generally more efficient to prepare a bunch of tuples for
+ * insertion, and insert them in bulk, for example, with one
+ * table_multi_insert() call than call table_tuple_insert() separately
+ * for every tuple. However, there are a number of reasons why we might
+ * not be able to do this. We check some conditions below while some
+ * other target relation properties are left for InitResultRelInfo() to
+ * check, because they must also be checked for partitions which are
+ * initialized later.
+ */
+ if (cstate->volatile_defexprs || list_length(cstate->attnumlist) == 0)
+ {
+ /*
+ * Can't support bufferization of copy into foreign tables without any
+ * defined columns or if there are any volatile default expressions in the
+ * table. Similarly to the trigger case above, such expressions may query
+ * the table we're inserting into.
+ *
+ * Note: It does not matter if any partitions have any volatile
+ * default expressions as we use the defaults from the target of the
+ * COPY command.
+ */
+ use_multi_insert = false;
+ }
+ else if (contain_volatile_functions(cstate->whereClause))
+ {
+ /*
+ * Can't support multi-inserts if there are any volatile function
+ * expressions in WHERE clause. Similarly to the trigger case above,
+ * such expressions may query the table we're inserting into.
+ */
+ use_multi_insert = false;
+ }
+ else
+ {
+ /*
+ * Looks okay to try multi-insert, but that may change once we
+ * check few more properties in InitResultRelInfo().
+ *
+ * For partitioned tables, whether or not to use multi-insert depends
+ * on the individual parition's properties which are also checked in
+ * InitResultRelInfo().
+ */
+ use_multi_insert = true;
+ }
+
+ /*
* We need a ResultRelInfo so we can use the regular executor's
* index-entry-making machinery. (There used to be a huge amount of code
* here that basically duplicated execUtils.c ...)
@@ -2877,6 +2912,7 @@ CopyFrom(CopyState cstate)
cstate->rel,
1, /* must match rel's position in range_table */
NULL,
+ use_multi_insert,
0);
target_resultRelInfo = resultRelInfo;
@@ -2928,85 +2964,9 @@ CopyFrom(CopyState cstate)
cstate->qualexpr = ExecInitQual(castNode(List, cstate->whereClause),
&mtstate->ps);
- /*
- * It's generally more efficient to prepare a bunch of tuples for
- * insertion, and insert them in one table_multi_insert() call, than call
- * table_tuple_insert() separately for every tuple. However, there are a
- * number of reasons why we might not be able to do this. These are
- * explained below.
- */
- if (resultRelInfo->ri_TrigDesc != NULL &&
- (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
- resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
- {
- /*
- * Can't support multi-inserts when there are any BEFORE/INSTEAD OF
- * triggers on the table. Such triggers might query the table we're
- * inserting into and act differently if the tuples that have already
- * been processed and prepared for insertion are not there.
- */
- insertMethod = CIM_SINGLE;
- }
- else if (proute != NULL && resultRelInfo->ri_TrigDesc != NULL &&
- resultRelInfo->ri_TrigDesc->trig_insert_new_table)
- {
- /*
- * For partitioned tables we can't support multi-inserts when there
- * are any statement level insert triggers. It might be possible to
- * allow partitioned tables with such triggers in the future, but for
- * now, CopyMultiInsertInfoFlush expects that any before row insert
- * and statement level insert triggers are on the same relation.
- */
- insertMethod = CIM_SINGLE;
- }
- else if (cstate->volatile_defexprs || list_length(cstate->attnumlist) == 0)
- {
- /*
- * Can't support bufferization of copy into foreign tables without any
- * defined columns or if there are any volatile default expressions in the
- * table. Similarly to the trigger case above, such expressions may query
- * the table we're inserting into.
- *
- * Note: It does not matter if any partitions have any volatile
- * default expressions as we use the defaults from the target of the
- * COPY command.
- */
- insertMethod = CIM_SINGLE;
- }
- else if (contain_volatile_functions(cstate->whereClause))
- {
- /*
- * Can't support multi-inserts if there are any volatile function
- * expressions in WHERE clause. Similarly to the trigger case above,
- * such expressions may query the table we're inserting into.
- */
- insertMethod = CIM_SINGLE;
- }
- else
- {
- /*
- * For partitioned tables, we may still be able to perform bulk
- * inserts. However, the possibility of this depends on which types
- * of triggers exist on the partition. We must disable bulk inserts
- * if the partition is a foreign table or it has any before row insert
- * or insert instead triggers (same as we checked above for the parent
- * table). Since the partition's resultRelInfos are initialized only
- * when we actually need to insert the first tuple into them, we must
- * have the intermediate insert method of CIM_MULTI_CONDITIONAL to
- * flag that we must later determine if we can use bulk-inserts for
- * the partition being inserted into.
- */
- if (proute)
- insertMethod = CIM_MULTI_CONDITIONAL;
- else
- insertMethod = CIM_MULTI;
-
+ if (resultRelInfo->ri_usesMultiInsert)
CopyMultiInsertInfoInit(&multiInsertInfo, resultRelInfo, cstate,
estate, mycid, ti_options);
- }
-
- if (insertMethod != CIM_SINGLE)
- resultRelInfo->ri_usesBulkModify = true;
/*
* Init COPY into foreign table. Initialization of copying into foreign
@@ -3014,7 +2974,7 @@ CopyFrom(CopyState cstate)
*/
if (target_resultRelInfo->ri_FdwRoutine != NULL)
{
- if (target_resultRelInfo->ri_usesBulkModify &&
+ if (target_resultRelInfo->ri_usesMultiInsert &&
target_resultRelInfo->ri_FdwRoutine->BeginForeignCopyIn != NULL)
target_resultRelInfo->ri_FdwRoutine->BeginForeignCopyIn(mtstate,
resultRelInfo);
@@ -3029,7 +2989,7 @@ CopyFrom(CopyState cstate)
* one, even if we might batch insert, to read the tuple in the root
* partition's form.
*/
- if (insertMethod == CIM_SINGLE || insertMethod == CIM_MULTI_CONDITIONAL)
+ if (!resultRelInfo->ri_usesMultiInsert || proute)
{
singleslot = table_slot_create(resultRelInfo->ri_RelationDesc,
&estate->es_tupleTable);
@@ -3072,7 +3032,7 @@ CopyFrom(CopyState cstate)
ResetPerTupleExprContext(estate);
/* select slot to (initially) load row into */
- if (insertMethod == CIM_SINGLE || proute)
+ if (!target_resultRelInfo->ri_usesMultiInsert || proute)
{
myslot = singleslot;
Assert(myslot != NULL);
@@ -3080,7 +3040,6 @@ CopyFrom(CopyState cstate)
else
{
Assert(resultRelInfo == target_resultRelInfo);
- Assert(insertMethod == CIM_MULTI);
myslot = CopyMultiInsertInfoNextFreeSlot(&multiInsertInfo,
resultRelInfo);
@@ -3139,24 +3098,14 @@ CopyFrom(CopyState cstate)
has_instead_insert_row_trig = (resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_instead_row);
- /*
- * Disable multi-inserts when the partition has BEFORE/INSTEAD
- * OF triggers, or if the partition is a foreign partition.
- */
- leafpart_use_multi_insert = insertMethod == CIM_MULTI_CONDITIONAL &&
- !has_before_insert_row_trig &&
- !has_instead_insert_row_trig &&
- (resultRelInfo->ri_FdwRoutine == NULL || resultRelInfo->ri_usesBulkModify);
-
/* Set the multi-insert buffer to use for this partition. */
- if (leafpart_use_multi_insert)
+ if (resultRelInfo->ri_usesMultiInsert)
{
if (resultRelInfo->ri_CopyMultiInsertBuffer == NULL)
CopyMultiInsertInfoSetupBuffer(&multiInsertInfo,
resultRelInfo);
}
- else if (insertMethod == CIM_MULTI_CONDITIONAL &&
- !CopyMultiInsertInfoIsEmpty(&multiInsertInfo))
+ else if (!CopyMultiInsertInfoIsEmpty(&multiInsertInfo))
{
/*
* Flush pending inserts if this partition can't use
@@ -3208,7 +3157,7 @@ CopyFrom(CopyState cstate)
* rowtype.
*/
map = resultRelInfo->ri_PartitionInfo->pi_RootToPartitionMap;
- if (insertMethod == CIM_SINGLE || !leafpart_use_multi_insert)
+ if (!resultRelInfo->ri_usesMultiInsert)
{
/* non batch insert */
if (map != NULL)
@@ -3227,9 +3176,6 @@ CopyFrom(CopyState cstate)
*/
TupleTableSlot *batchslot;
- /* no other path available for partitioned table */
- Assert(insertMethod == CIM_MULTI_CONDITIONAL);
-
batchslot = CopyMultiInsertInfoNextFreeSlot(&multiInsertInfo,
resultRelInfo);
@@ -3300,7 +3246,7 @@ CopyFrom(CopyState cstate)
ExecPartitionCheck(resultRelInfo, myslot, estate, true);
/* Store the slot in the multi-insert buffer, when enabled. */
- if (insertMethod == CIM_MULTI || leafpart_use_multi_insert)
+ if (resultRelInfo->ri_usesMultiInsert)
{
/*
* The slot previously might point into the per-tuple
@@ -3375,11 +3321,8 @@ CopyFrom(CopyState cstate)
}
/* Flush any remaining buffered tuples */
- if (insertMethod != CIM_SINGLE)
- {
- if (!CopyMultiInsertInfoIsEmpty(&multiInsertInfo))
- CopyMultiInsertInfoFlush(&multiInsertInfo, NULL);
- }
+ if (!CopyMultiInsertInfoIsEmpty(&multiInsertInfo))
+ CopyMultiInsertInfoFlush(&multiInsertInfo, NULL);
/* Done, clean up */
error_context_stack = errcallback.previous;
@@ -3407,19 +3350,17 @@ CopyFrom(CopyState cstate)
/* Allow the FDW to shut down */
if (target_resultRelInfo->ri_FdwRoutine != NULL)
{
- if (target_resultRelInfo->ri_usesBulkModify &&
+ if (target_resultRelInfo->ri_usesMultiInsert &&
target_resultRelInfo->ri_FdwRoutine->EndForeignCopyIn != NULL)
target_resultRelInfo->ri_FdwRoutine->EndForeignCopyIn(estate,
target_resultRelInfo);
else if (target_resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
target_resultRelInfo->ri_FdwRoutine->EndForeignInsert(estate,
target_resultRelInfo);
- target_resultRelInfo->ri_usesBulkModify = false;
}
/* Tear down the multi-insert buffer data */
- if (insertMethod != CIM_SINGLE)
- CopyMultiInsertInfoCleanup(&multiInsertInfo);
+ CopyMultiInsertInfoCleanup(&multiInsertInfo);
ExecCloseIndices(target_resultRelInfo);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ac53f79..f071b56 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1786,6 +1786,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
rel,
0, /* dummy rangetable index */
NULL,
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b8b09d5..e08c8b2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -851,6 +851,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
resultRelation,
resultRelationIndex,
NULL,
+ false,
estate->es_instrument);
resultRelInfo++;
}
@@ -883,6 +884,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
resultRelDesc,
resultRelIndex,
NULL,
+ false,
estate->es_instrument);
resultRelInfo++;
}
@@ -1278,6 +1280,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
Relation partition_root,
+ bool use_multi_insert,
int instrument_options)
{
List *partition_check = NIL;
@@ -1345,7 +1348,55 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_PartitionRoot = partition_root;
resultRelInfo->ri_PartitionInfo = NULL; /* may be set later */
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
- resultRelInfo->ri_usesBulkModify = false;
+
+ /*
+ * If the caller has asked to use "multi-insert" mode, check if the
+ * relation allows it and if it does set ri_usesMultiInsert to true.
+ */
+ if (!use_multi_insert)
+ {
+ /* Caller didn't ask for it. */
+ resultRelInfo->ri_usesMultiInsert = false;
+ }
+ else if (resultRelInfo->ri_TrigDesc != NULL &&
+ (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
+ resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
+ {
+ /*
+ * Can't support multi-inserts when there are any BEFORE/INSTEAD OF
+ * triggers on the table. Such triggers might query the table we're
+ * inserting into and act differently if the tuples that have already
+ * been processed and prepared for insertion are not there.
+ */
+ resultRelInfo->ri_usesMultiInsert = false;
+ }
+ else if (resultRelationDesc->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ resultRelInfo->ri_TrigDesc != NULL &&
+ resultRelInfo->ri_TrigDesc->trig_insert_new_table)
+ {
+ /*
+ * For partitioned tables we can't support multi-inserts when there
+ * are any statement level insert triggers. It might be possible to
+ * allow partitioned tables with such triggers in the future, but for
+ * now, CopyMultiInsertInfoFlush expects that any before row insert
+ * and statement level insert triggers are on the same relation.
+ */
+ resultRelInfo->ri_usesMultiInsert = false;
+ }
+ else if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->ExecForeignCopyIn == NULL)
+ {
+ /*
+ * For a foreign table, we can't support multi-inserts unless its FDW
+ * provides the necessary COPY interface.
+ */
+ resultRelInfo->ri_usesMultiInsert = false;
+ }
+ else
+ {
+ /* OK, caller can use multi-insert on this relation. */
+ resultRelInfo->ri_usesMultiInsert = true;
+ }
}
/*
@@ -1435,6 +1486,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
rel,
0, /* dummy rangetable index */
NULL,
+ false,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 6a4b947..7b72a09 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -524,13 +524,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
partrel,
node ? node->rootRelation : 1,
rootrel,
+ rootResultRelInfo->ri_usesMultiInsert,
estate->es_instrument);
- if (rootResultRelInfo->ri_usesBulkModify &&
- leaf_part_rri->ri_FdwRoutine != NULL &&
- leaf_part_rri->ri_FdwRoutine->BeginForeignCopyIn != NULL)
- leaf_part_rri->ri_usesBulkModify = true;
-
/*
* Verify result relation is a valid target for an INSERT. An UPDATE of a
* partition-key becomes a DELETE+INSERT operation, so this check is still
@@ -944,11 +940,9 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
*/
if (partRelInfo->ri_FdwRoutine != NULL)
{
- if (partRelInfo->ri_usesBulkModify)
- {
- Assert(partRelInfo->ri_FdwRoutine->BeginForeignCopyIn != NULL);
+ if (partRelInfo->ri_usesMultiInsert &&
+ partRelInfo->ri_FdwRoutine->BeginForeignCopyIn != NULL)
partRelInfo->ri_FdwRoutine->BeginForeignCopyIn(mtstate, partRelInfo);
- }
else if (partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
}
@@ -1135,7 +1129,7 @@ ExecCleanupTupleRouting(ModifyTableState *mtstate,
/* Allow any FDWs to shut down */
if (resultRelInfo->ri_FdwRoutine != NULL)
{
- if (resultRelInfo->ri_usesBulkModify)
+ if (resultRelInfo->ri_usesMultiInsert)
{
Assert(resultRelInfo->ri_FdwRoutine->EndForeignCopyIn != NULL);
resultRelInfo->ri_FdwRoutine->EndForeignCopyIn(mtstate->ps.state,
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 2fcf2e6..d33c9c9 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -211,7 +211,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
ExecInitRangeTable(estate, list_make1(rte));
resultRelInfo = makeNode(ResultRelInfo);
- InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+ InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, false, 0);
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 415e117..72612bd 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,6 +189,7 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
Relation partition_root,
+ bool use_multi_insert,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecCleanUpTriggerState(EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb3b2f0..1d79b6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -489,16 +489,15 @@ typedef struct ResultRelInfo
/* Additional information specific to partition tuple routing */
struct PartitionRoutingInfo *ri_PartitionInfo;
- /* For use by copy.c when performing multi-inserts */
- struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
-
/*
- * For use by copy.c:
- * for partitioned relation "true" means that child relations are allowed for
- * using bulk modify operations; for foreign relation (or foreign partition
- * of) "true" value means that modify operations must use bulk FDW API.
+ * The following fields are currently only relevant to copy.c.
+ *
+ * True if okay to use multi-insert on this relation
*/
- bool ri_usesBulkModify;
+ bool ri_usesMultiInsert;
+
+ /* Buffer allocated to this relation when using multi-insert mode */
+ struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
} ResultRelInfo;
/* ----------------