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; /* ----------------