*** a/contrib/file_fdw/output/file_fdw.source --- b/contrib/file_fdw/output/file_fdw.source *************** *** 315,321 **** SELECT tableoid::regclass, * FROM p2; (0 rows) COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR ! ERROR: cannot route inserted tuples to a foreign table CONTEXT: COPY pt, line 2: "1,qux" COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ','); SELECT tableoid::regclass, * FROM pt; --- 315,321 ---- (0 rows) COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR ! ERROR: cannot route copied tuples to a foreign table CONTEXT: COPY pt, line 2: "1,qux" COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ','); SELECT tableoid::regclass, * FROM pt; *************** *** 342,348 **** SELECT tableoid::regclass, * FROM p2; (2 rows) INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR ! ERROR: cannot route inserted tuples to a foreign table INSERT INTO pt VALUES (2, 'xyzzy'); SELECT tableoid::regclass, * FROM pt; tableoid | a | b --- 342,348 ---- (2 rows) INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR ! ERROR: cannot route inserted tuples to foreign table "p1" INSERT INTO pt VALUES (2, 'xyzzy'); SELECT tableoid::regclass, * FROM pt; tableoid | a | b *** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** *** 7029,7034 **** NOTICE: drop cascades to foreign table bar2 --- 7029,7172 ---- drop table loct1; drop table loct2; -- =================================================================== + -- test tuple routing to foreign-table partitions + -- =================================================================== + create table pt (a int, b int) partition by list (a); + create table locp partition of pt for values in (1); + create table locfoo (a int check (a in (2)), b int); + create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo'); + explain (verbose, costs off) + insert into pt values (1, 1), (2, 1); + QUERY PLAN + ----------------------------------------------------------------- + Insert on public.pt + Insert on public.locp + Foreign Insert on public.remp + Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2) + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 + (6 rows) + + insert into pt values (1, 1), (2, 1); + select tableoid::regclass, * FROM pt; + tableoid | a | b + ----------+---+--- + locp | 1 | 1 + remp | 2 | 1 + (2 rows) + + select tableoid::regclass, * FROM locp; + tableoid | a | b + ----------+---+--- + locp | 1 | 1 + (1 row) + + select tableoid::regclass, * FROM remp; + tableoid | a | b + ----------+---+--- + remp | 2 | 1 + (1 row) + + explain (verbose, costs off) + insert into pt values (1, 2), (2, 2) returning *; + QUERY PLAN + -------------------------------------------------------------------------------- + Insert on public.pt + Output: pt.a, pt.b + Insert on public.locp + Foreign Insert on public.remp + Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2) RETURNING a, b + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 + (7 rows) + + insert into pt values (1, 2), (2, 2) returning *; + a | b + ---+--- + 1 | 2 + 2 | 2 + (2 rows) + + select tableoid::regclass, * FROM pt; + tableoid | a | b + ----------+---+--- + locp | 1 | 1 + locp | 1 | 2 + remp | 2 | 1 + remp | 2 | 2 + (4 rows) + + select tableoid::regclass, * FROM locp; + tableoid | a | b + ----------+---+--- + locp | 1 | 1 + locp | 1 | 2 + (2 rows) + + select tableoid::regclass, * FROM remp; + tableoid | a | b + ----------+---+--- + remp | 2 | 1 + remp | 2 | 2 + (2 rows) + + prepare q1 as insert into pt values (1, 3), (2, 3); + explain (verbose, costs off) execute q1; + QUERY PLAN + ----------------------------------------------------------------- + Insert on public.pt + Insert on public.locp + Foreign Insert on public.remp + Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2) + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 + (6 rows) + + alter table locfoo rename to locbar; + alter foreign table remp options (set table_name 'locbar'); + explain (verbose, costs off) execute q1; + QUERY PLAN + ----------------------------------------------------------------- + Insert on public.pt + Insert on public.locp + Foreign Insert on public.remp + Remote SQL: INSERT INTO public.locbar(a, b) VALUES ($1, $2) + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 + (6 rows) + + execute q1; + select tableoid::regclass, * FROM pt; + tableoid | a | b + ----------+---+--- + locp | 1 | 1 + locp | 1 | 2 + locp | 1 | 3 + remp | 2 | 1 + remp | 2 | 2 + remp | 2 | 3 + (6 rows) + + select tableoid::regclass, * FROM locp; + tableoid | a | b + ----------+---+--- + locp | 1 | 1 + locp | 1 | 2 + locp | 1 | 3 + (3 rows) + + select tableoid::regclass, * FROM remp; + tableoid | a | b + ----------+---+--- + remp | 2 | 1 + remp | 2 | 2 + remp | 2 | 3 + (3 rows) + + deallocate q1; + drop table pt; + drop table locbar; + -- =================================================================== -- test IMPORT FOREIGN SCHEMA -- =================================================================== CREATE SCHEMA import_source; *** a/contrib/postgres_fdw/sql/postgres_fdw.sql --- b/contrib/postgres_fdw/sql/postgres_fdw.sql *************** *** 1662,1667 **** drop table loct1; --- 1662,1707 ---- drop table loct2; -- =================================================================== + -- test tuple routing to foreign-table partitions + -- =================================================================== + + create table pt (a int, b int) partition by list (a); + create table locp partition of pt for values in (1); + create table locfoo (a int check (a in (2)), b int); + create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo'); + + explain (verbose, costs off) + insert into pt values (1, 1), (2, 1); + insert into pt values (1, 1), (2, 1); + + select tableoid::regclass, * FROM pt; + select tableoid::regclass, * FROM locp; + select tableoid::regclass, * FROM remp; + + explain (verbose, costs off) + insert into pt values (1, 2), (2, 2) returning *; + insert into pt values (1, 2), (2, 2) returning *; + + select tableoid::regclass, * FROM pt; + select tableoid::regclass, * FROM locp; + select tableoid::regclass, * FROM remp; + + prepare q1 as insert into pt values (1, 3), (2, 3); + explain (verbose, costs off) execute q1; + alter table locfoo rename to locbar; + alter foreign table remp options (set table_name 'locbar'); + explain (verbose, costs off) execute q1; + execute q1; + + select tableoid::regclass, * FROM pt; + select tableoid::regclass, * FROM locp; + select tableoid::regclass, * FROM remp; + + deallocate q1; + drop table pt; + drop table locbar; + + -- =================================================================== -- test IMPORT FOREIGN SCHEMA -- =================================================================== *** a/src/backend/commands/copy.c --- b/src/backend/commands/copy.c *************** *** 2417,2422 **** CopyFrom(CopyState cstate) --- 2417,2423 ---- cstate->rel, 1, /* dummy rangetable index */ NULL, + 0, /* dummy rangetable index */ 0); ExecOpenIndices(resultRelInfo, false); *************** *** 2446,2461 **** CopyFrom(CopyState cstate) if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { PartitionDispatch *partition_dispatch_info; ResultRelInfo *partitions; TupleConversionMap **partition_tupconv_maps; TupleTableSlot *partition_tuple_slot; int num_parted, num_partitions; ExecSetupPartitionTupleRouting(cstate->rel, - 1, - estate, &partition_dispatch_info, &partitions, &partition_tupconv_maps, &partition_tuple_slot, --- 2447,2465 ---- if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { PartitionDispatch *partition_dispatch_info; + List *partition_oids; ResultRelInfo *partitions; TupleConversionMap **partition_tupconv_maps; TupleTableSlot *partition_tuple_slot; int num_parted, num_partitions; + ResultRelInfo *partRelInfo; + int i; + ListCell *l; ExecSetupPartitionTupleRouting(cstate->rel, &partition_dispatch_info, + &partition_oids, &partitions, &partition_tupconv_maps, &partition_tuple_slot, *************** *** 2467,2472 **** CopyFrom(CopyState cstate) --- 2471,2508 ---- cstate->partition_tupconv_maps = partition_tupconv_maps; cstate->partition_tuple_slot = partition_tuple_slot; + partRelInfo = partitions; + i = 0; + foreach(l, partition_oids) + { + Oid partOid = lfirst_oid(l); + Relation partrel; + + /* Prepare map and ResultRelInfo for the partition */ + ExecInitPartition(estate, + partOid, + 0, /* dummy rangetable index */ + resultRelInfo, + partRelInfo, + &partition_tupconv_maps[i]); + + /* Verify the partition is a valid target for COPY */ + partrel = partRelInfo->ri_RelationDesc; + if (partrel->rd_rel->relkind == RELKIND_RELATION) + partRelInfo->ri_PartitionIsValid = true; + else + { + /* Should be foreign */ + Assert(partrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE); + + /* We do not yet have a way to copy into a foreign partition */ + partRelInfo->ri_PartitionIsValid = false; + } + + partRelInfo++; + i++; + } + /* * If we are capturing transition tuples, they may need to be * converted from partition format back to partitioned table format *************** *** 2618,2628 **** CopyFrom(CopyState cstate) saved_resultRelInfo = resultRelInfo; resultRelInfo = cstate->partitions + leaf_part_index; ! /* We do not yet have a way to insert into a foreign partition */ ! if (resultRelInfo->ri_FdwRoutine) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot route inserted tuples to a foreign table"))); /* * For ExecInsertIndexTuples() to work on the partition's indexes --- 2654,2669 ---- saved_resultRelInfo = resultRelInfo; resultRelInfo = cstate->partitions + leaf_part_index; ! /* We do not yet have a way to copy into a foreign partition */ ! if (!resultRelInfo->ri_PartitionIsValid) ! { ! /* Should be foreign */ ! Assert(resultRelInfo->ri_FdwRoutine); ! ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot route copied tuples to a foreign table"))); ! } /* * For ExecInsertIndexTuples() to work on the partition's indexes *** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** *** 116,121 **** static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es); --- 116,125 ---- static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es); static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors, ExplainState *es); + static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node, + ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine, + bool main_target, int subplan_index, + const char *operation, bool labeltarget, ExplainState *es); static void ExplainMemberNodes(List *plans, PlanState **planstates, List *ancestors, ExplainState *es); static void ExplainSubPlans(List *plans, List *ancestors, *************** *** 834,839 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) --- 838,854 ---- if (((ModifyTable *) plan)->exclRelRTI) *rels_used = bms_add_member(*rels_used, ((ModifyTable *) plan)->exclRelRTI); + if (((ModifyTable *) plan)->partition_rels) + { + ListCell *lc; + + foreach(lc, ((ModifyTable *) plan)->partition_rels) + { + Index rti = lfirst_int(lc); + + *rels_used = bms_add_member(*rels_used, rti); + } + } break; default: break; *************** *** 2856,2916 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors, 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; ! if (labeltargets) ! { ! /* Open a group for this target */ ! ExplainOpenGroup("Target Table", NULL, true, es); ! ! /* ! * In text mode, decorate each target with operation type, so that ! * ExplainTargetRel's output of " on foo" will read nicely. ! */ ! if (es->format == EXPLAIN_FORMAT_TEXT) ! { ! appendStringInfoSpaces(es->str, es->indent * 2); ! appendStringInfoString(es->str, ! fdwroutine ? foperation : operation); ! } ! ! /* Identify target */ ! ExplainTargetRel((Plan *) node, ! resultRelInfo->ri_RangeTableIndex, ! es); ! if (es->format == EXPLAIN_FORMAT_TEXT) ! { ! appendStringInfoChar(es->str, '\n'); ! es->indent++; ! } ! } ! /* Give FDW a chance if needed */ ! if (!resultRelInfo->ri_usesFdwDirectModify && ! fdwroutine != NULL && ! fdwroutine->ExplainForeignModify != NULL) { ! List *fdw_private = (List *) list_nth(node->fdwPrivLists, j); ! fdwroutine->ExplainForeignModify(mtstate, ! resultRelInfo, ! fdw_private, ! j, ! es); ! } ! if (labeltargets) ! { ! /* Undo the indentation we added in text format */ ! if (es->format == EXPLAIN_FORMAT_TEXT) ! es->indent--; ! /* Close the group */ ! ExplainCloseGroup("Target Table", NULL, true, es); } } --- 2871,2919 ---- if (labeltargets) ExplainOpenGroup("Target Tables", "Target Tables", false, es); + /* Print main target(s) */ for (j = 0; j < mtstate->mt_nplans; j++) { ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j; FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; ! show_actual_target(mtstate, node, resultRelInfo, fdwroutine, true, j, ! fdwroutine ? foperation : operation, labeltargets, ! es); ! } ! /* If this is an INSERT into a partitioned table, print partitions */ ! for (j = 0; j < mtstate->mt_num_partitions; j++) ! { ! ResultRelInfo *resultRelInfo = mtstate->mt_partitions + j; ! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; ! if (resultRelInfo->ri_PartitionIsValid) { ! Oid partOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); ! int k; ! ListCell *cell; ! bool found; ! /* First, find the subplan index for the partition */ ! found = false; ! k = 0; ! foreach(cell, node->partition_rels) ! { ! Index partRTindex = lfirst_int(cell); ! if (getrelid(partRTindex, es->rtable) == partOid) ! { ! found = true; ! break; ! } ! k++; ! } ! if (!found) ! elog(ERROR, "failed to find subplan index for relation %u", partOid); ! show_actual_target(mtstate, node, resultRelInfo, fdwroutine, false, k, ! fdwroutine ? foperation : operation, true, es); } } *************** *** 2968,2973 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors, --- 2971,3042 ---- } /* + * Show an actual target relation + */ + static void + show_actual_target(ModifyTableState *mtstate, ModifyTable *node, + ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine, + bool main_target, int subplan_index, + const char *operation, bool labeltarget, ExplainState *es) + { + if (labeltarget) + { + /* Open a group for this target */ + ExplainOpenGroup("Target Table", NULL, true, es); + + /* + * In text mode, decorate each target with operation type, so that + * ExplainTargetRel's output of " on foo" will read nicely. + */ + if (es->format == EXPLAIN_FORMAT_TEXT) + { + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfoString(es->str, operation); + } + + /* Identify target */ + ExplainTargetRel((Plan *) node, + resultRelInfo->ri_RangeTableIndex, + es); + + if (es->format == EXPLAIN_FORMAT_TEXT) + { + appendStringInfoChar(es->str, '\n'); + es->indent++; + } + } + + /* Give FDW a chance if needed */ + if (fdwroutine != NULL && + fdwroutine->ExplainForeignModify != NULL && + !resultRelInfo->ri_usesFdwDirectModify) + { + List *fdw_private; + + if (main_target) + fdw_private = (List *) list_nth(node->fdwPrivLists, subplan_index); + else + fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, subplan_index); + + fdwroutine->ExplainForeignModify(mtstate, + resultRelInfo, + fdw_private, + main_target ? subplan_index : 0, + es); + } + + if (labeltarget) + { + /* Undo the indentation we added in text format */ + if (es->format == EXPLAIN_FORMAT_TEXT) + es->indent--; + + /* Close the group */ + ExplainCloseGroup("Target Table", NULL, true, es); + } + } + + /* * Explain the constituent plans of a ModifyTable, Append, MergeAppend, * BitmapAnd, or BitmapOr node. * *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 1403,1408 **** ExecuteTruncate(TruncateStmt *stmt) --- 1403,1409 ---- rel, 0, /* dummy rangetable index */ NULL, + 0, /* dummy rangetable index */ 0); resultRelInfo++; } *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 114,120 **** static void ExecPartitionCheck(ResultRelInfo *resultRelInfo, * to be changed, however. */ #define GetInsertedColumns(relinfo, estate) \ ! (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols) #define GetUpdatedColumns(relinfo, estate) \ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols) --- 114,123 ---- * to be changed, however. */ #define GetInsertedColumns(relinfo, estate) \ ! (rt_fetch((relinfo)->ri_PartitionRoot ? \ ! (relinfo)->ri_PartitionRootRTindex : \ ! (relinfo)->ri_RangeTableIndex, \ ! (estate)->es_range_table)->insertedCols) #define GetUpdatedColumns(relinfo, estate) \ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols) *************** *** 854,859 **** InitPlan(QueryDesc *queryDesc, int eflags) --- 857,863 ---- resultRelation, resultRelationIndex, NULL, + 0, /* dummy rangetable index */ estate->es_instrument); resultRelInfo++; } *************** *** 893,898 **** InitPlan(QueryDesc *queryDesc, int eflags) --- 897,903 ---- resultRelDesc, lfirst_int(l), NULL, + 0, /* dummy rangetable index */ estate->es_instrument); resultRelInfo++; } *************** *** 1102,1113 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation) --- 1107,1121 ---- Relation resultRel = resultRelInfo->ri_RelationDesc; TriggerDesc *trigDesc = resultRel->trigdesc; FdwRoutine *fdwroutine; + bool is_valid; switch (resultRel->rd_rel->relkind) { case RELKIND_RELATION: case RELKIND_PARTITIONED_TABLE: CheckCmdReplicaIdentity(resultRel, operation); + if (resultRelInfo->ri_PartitionRoot && operation == CMD_INSERT) + resultRelInfo->ri_PartitionIsValid = true; break; case RELKIND_SEQUENCE: ereport(ERROR, *************** *** 1174,1196 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation) switch (operation) { case CMD_INSERT: ! /* ! * If foreign partition to do tuple-routing for, skip the ! * check; it's disallowed elsewhere. ! */ ! if (resultRelInfo->ri_PartitionRoot) ! break; if (fdwroutine->ExecForeignInsert == NULL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot insert into foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); if (fdwroutine->IsForeignRelUpdatable != NULL && (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0) ! ereport(ERROR, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("foreign table \"%s\" does not allow inserts", ! RelationGetRelationName(resultRel)))); break; case CMD_UPDATE: if (fdwroutine->ExecForeignUpdate == NULL) --- 1182,1209 ---- switch (operation) { case CMD_INSERT: ! is_valid = true; if (fdwroutine->ExecForeignInsert == NULL) ! { ! if (!resultRelInfo->ri_PartitionRoot) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot insert into foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); ! is_valid = false; ! } if (fdwroutine->IsForeignRelUpdatable != NULL && (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0) ! { ! if (!resultRelInfo->ri_PartitionRoot) ! ereport(ERROR, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("foreign table \"%s\" does not allow inserts", ! RelationGetRelationName(resultRel)))); ! is_valid = false; ! } ! if (resultRelInfo->ri_PartitionRoot) ! resultRelInfo->ri_PartitionIsValid = is_valid; break; case CMD_UPDATE: if (fdwroutine->ExecForeignUpdate == NULL) *************** *** 1308,1313 **** InitResultRelInfo(ResultRelInfo *resultRelInfo, --- 1321,1327 ---- Relation resultRelationDesc, Index resultRelationIndex, Relation partition_root, + Index partition_root_rtindex, int instrument_options) { List *partition_check = NIL; *************** *** 1365,1370 **** InitResultRelInfo(ResultRelInfo *resultRelInfo, --- 1379,1386 ---- resultRelInfo->ri_PartitionCheck = partition_check; resultRelInfo->ri_PartitionRoot = partition_root; + resultRelInfo->ri_PartitionRootRTindex = partition_root_rtindex; + resultRelInfo->ri_PartitionIsValid = false; } /* *************** *** 1447,1452 **** ExecGetTriggerResultRel(EState *estate, Oid relid) --- 1463,1469 ---- rel, 0, /* dummy rangetable index */ NULL, + 0, /* dummy rangetable index */ estate->es_instrument); estate->es_trig_target_relations = lappend(estate->es_trig_target_relations, rInfo); *************** *** 3245,3250 **** EvalPlanQualEnd(EPQState *epqstate) --- 3262,3269 ---- * Output arguments: * 'pd' receives an array of PartitionDispatch objects with one entry for * every partitioned table in the partition tree + * 'leaf_parts' receives a list of relation OIDs with one entry for every leaf + * partition in the partition tree * 'partitions' receives an array of ResultRelInfo objects with one entry for * every leaf partition in the partition tree * 'tup_conv_maps' receives an array of TupleConversionMap objects with one *************** *** 3265,3291 **** EvalPlanQualEnd(EPQState *epqstate) */ void ExecSetupPartitionTupleRouting(Relation rel, - Index resultRTindex, - EState *estate, PartitionDispatch **pd, ResultRelInfo **partitions, TupleConversionMap ***tup_conv_maps, TupleTableSlot **partition_tuple_slot, int *num_parted, int *num_partitions) { - TupleDesc tupDesc = RelationGetDescr(rel); - List *leaf_parts; - ListCell *cell; - int i; - ResultRelInfo *leaf_part_rri; - /* * Get the information about the partition tree after locking all the * partitions. */ (void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL); ! *pd = RelationGetPartitionDispatchInfo(rel, num_parted, &leaf_parts); ! *num_partitions = list_length(leaf_parts); *partitions = (ResultRelInfo *) palloc(*num_partitions * sizeof(ResultRelInfo)); *tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions * --- 3284,3303 ---- */ void ExecSetupPartitionTupleRouting(Relation rel, PartitionDispatch **pd, + List **leaf_parts, ResultRelInfo **partitions, TupleConversionMap ***tup_conv_maps, TupleTableSlot **partition_tuple_slot, int *num_parted, int *num_partitions) { /* * Get the information about the partition tree after locking all the * partitions. */ (void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL); ! *pd = RelationGetPartitionDispatchInfo(rel, num_parted, leaf_parts); ! *num_partitions = list_length(*leaf_parts); *partitions = (ResultRelInfo *) palloc(*num_partitions * sizeof(ResultRelInfo)); *tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions * *************** *** 3298,3352 **** ExecSetupPartitionTupleRouting(Relation rel, * processing. */ *partition_tuple_slot = MakeTupleTableSlot(); ! leaf_part_rri = *partitions; ! i = 0; ! foreach(cell, leaf_parts) ! { ! Relation partrel; ! TupleDesc part_tupdesc; ! ! /* ! * We locked all the partitions above including the leaf partitions. ! * Note that each of the relations in *partitions are eventually ! * closed by the caller. ! */ ! partrel = heap_open(lfirst_oid(cell), NoLock); ! part_tupdesc = RelationGetDescr(partrel); ! ! /* ! * Save a tuple conversion map to convert a tuple routed to this ! * partition from the parent's type to the partition's. ! */ ! (*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc, ! gettext_noop("could not convert row type")); ! InitResultRelInfo(leaf_part_rri, ! partrel, ! resultRTindex, ! rel, ! estate->es_instrument); ! /* ! * Verify result relation is a valid target for INSERT. ! */ ! CheckValidResultRel(leaf_part_rri, CMD_INSERT); ! /* ! * Open partition indices (remember we do not support ON CONFLICT in ! * case of partitioned tables, so we do not need support information ! * for speculative insertion) ! */ ! if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex && ! leaf_part_rri->ri_IndexRelationDescs == NULL) ! ExecOpenIndices(leaf_part_rri, false); ! estate->es_leaf_result_relations = ! lappend(estate->es_leaf_result_relations, leaf_part_rri); ! leaf_part_rri++; ! i++; ! } } /* --- 3310,3366 ---- * processing. */ *partition_tuple_slot = MakeTupleTableSlot(); + } ! /* ! * ExecInitPartition -- Prepare tuple conversion map and ResultRelInfo for ! * the partition with OID 'partOid' ! */ ! void ! ExecInitPartition(EState *estate, ! Oid partOid, ! Index partRTindex, ! ResultRelInfo *rootRelInfo, ! ResultRelInfo *partRelInfo, ! TupleConversionMap **partTupConvMap) ! { ! Relation rootrel = rootRelInfo->ri_RelationDesc; ! Relation partrel; ! /* ! * We assume that ExecSetupPartitionTupleRouting() already locked the ! * partition, so we need no lock here. The partition must be closed ! * by the caller. ! */ ! partrel = heap_open(partOid, NoLock); ! /* ! * Save a tuple conversion map to convert a tuple routed to the partition ! * from the parent's type to the partition's. ! */ ! *partTupConvMap = convert_tuples_by_name(RelationGetDescr(rootrel), ! RelationGetDescr(partrel), ! gettext_noop("could not convert row type")); ! /* Save a ResultRelInfo data for the partition. */ ! InitResultRelInfo(partRelInfo, ! partrel, ! partRTindex, ! rootrel, ! rootRelInfo->ri_RangeTableIndex, ! estate->es_instrument); ! /* ! * Open partition indices (remember we do not support ON CONFLICT in ! * case of partitioned tables, so we do not need support information ! * for speculative insertion) ! */ ! if (partRelInfo->ri_RelationDesc->rd_rel->relhasindex && ! partRelInfo->ri_IndexRelationDescs == NULL) ! ExecOpenIndices(partRelInfo, false); ! estate->es_leaf_result_relations = ! lappend(estate->es_leaf_result_relations, partRelInfo); } /* *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 305,315 **** ExecInsert(ModifyTableState *mtstate, saved_resultRelInfo = resultRelInfo; resultRelInfo = mtstate->mt_partitions + leaf_part_index; ! /* We do not yet have a way to insert into a foreign partition */ ! if (resultRelInfo->ri_FdwRoutine) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot route inserted tuples to a foreign table"))); /* For ExecInsertIndexTuples() to work on the partition's indexes */ estate->es_result_relation_info = resultRelInfo; --- 305,321 ---- saved_resultRelInfo = resultRelInfo; resultRelInfo = mtstate->mt_partitions + leaf_part_index; ! if (!resultRelInfo->ri_PartitionIsValid) ! { ! /* Should be foreign */ ! Assert(resultRelInfo->ri_FdwRoutine); ! ! /* We cannot insert into this foreign partition */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot route inserted tuples to foreign table \"%s\"", ! RelationGetRelationName(resultRelInfo->ri_RelationDesc)))); ! } /* For ExecInsertIndexTuples() to work on the partition's indexes */ estate->es_result_relation_info = resultRelInfo; *************** *** 1912,1927 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { PartitionDispatch *partition_dispatch_info; ResultRelInfo *partitions; TupleConversionMap **partition_tupconv_maps; TupleTableSlot *partition_tuple_slot; int num_parted, num_partitions; ExecSetupPartitionTupleRouting(rel, - node->nominalRelation, - estate, &partition_dispatch_info, &partitions, &partition_tupconv_maps, &partition_tuple_slot, --- 1918,1935 ---- rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { PartitionDispatch *partition_dispatch_info; + List *partition_oids; ResultRelInfo *partitions; TupleConversionMap **partition_tupconv_maps; TupleTableSlot *partition_tuple_slot; int num_parted, num_partitions; + Index rootRTindex; + ResultRelInfo *partRelInfo; ExecSetupPartitionTupleRouting(rel, &partition_dispatch_info, + &partition_oids, &partitions, &partition_tupconv_maps, &partition_tuple_slot, *************** *** 1932,1937 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) --- 1940,2002 ---- mtstate->mt_num_partitions = num_partitions; mtstate->mt_partition_tupconv_maps = partition_tupconv_maps; mtstate->mt_partition_tuple_slot = partition_tuple_slot; + + rootRTindex = mtstate->resultRelInfo->ri_RangeTableIndex; + partRelInfo = partitions; + i = 0; + foreach(l, partition_oids) + { + Oid partOid = lfirst_oid(l); + Index partRTindex; + int j; + ListCell *cell; + bool found; + + /* First, find the RT index for the partition */ + found = false; + j = 0; + foreach(cell, node->partition_rels) + { + partRTindex = lfirst_int(cell); + + if (getrelid(partRTindex, estate->es_range_table) == partOid) + { + found = true; + break; + } + j++; + } + if (!found) + elog(ERROR, "failed to find range table index for relation %u", partOid); + + /* Prepare map and ResultRelInfo for the partition */ + ExecInitPartition(estate, + partOid, + partRTindex, + mtstate->resultRelInfo, + partRelInfo, + &partition_tupconv_maps[i]); + + /* Verify the partition is a valid target for INSERT */ + CheckValidResultRel(partRelInfo, CMD_INSERT); + + /* If so, let the FDW init itself for the partition */ + if (partRelInfo->ri_PartitionIsValid && + partRelInfo->ri_FdwRoutine != NULL && + partRelInfo->ri_FdwRoutine->BeginForeignModify != NULL) + { + List *fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, j); + + partRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate, + partRelInfo, + fdw_private, + 0, + eflags); + } + + partRelInfo++; + i++; + } } /* Build state for collecting transition tuples */ *************** *** 2361,2366 **** ExecEndModifyTable(ModifyTableState *node) --- 2426,2436 ---- { ResultRelInfo *resultRelInfo = node->mt_partitions + i; + if (resultRelInfo->ri_FdwRoutine != NULL && + resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) + resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state, + resultRelInfo); + ExecCloseIndices(resultRelInfo); heap_close(resultRelInfo->ri_RelationDesc, NoLock); } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 204,209 **** _copyModifyTable(const ModifyTable *from) --- 204,210 ---- COPY_SCALAR_FIELD(canSetTag); COPY_SCALAR_FIELD(nominalRelation); COPY_NODE_FIELD(partitioned_rels); + COPY_NODE_FIELD(partition_rels); COPY_NODE_FIELD(resultRelations); COPY_SCALAR_FIELD(resultRelIndex); COPY_SCALAR_FIELD(rootResultRelIndex); *************** *** 220,225 **** _copyModifyTable(const ModifyTable *from) --- 221,227 ---- COPY_NODE_FIELD(onConflictWhere); COPY_SCALAR_FIELD(exclRelRTI); COPY_NODE_FIELD(exclRelTlist); + COPY_NODE_FIELD(fdwPartitionPrivLists); return newnode; } *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 367,372 **** _outModifyTable(StringInfo str, const ModifyTable *node) --- 367,373 ---- WRITE_BOOL_FIELD(canSetTag); WRITE_UINT_FIELD(nominalRelation); WRITE_NODE_FIELD(partitioned_rels); + WRITE_NODE_FIELD(partition_rels); WRITE_NODE_FIELD(resultRelations); WRITE_INT_FIELD(resultRelIndex); WRITE_INT_FIELD(rootResultRelIndex); *************** *** 383,388 **** _outModifyTable(StringInfo str, const ModifyTable *node) --- 384,390 ---- WRITE_NODE_FIELD(onConflictWhere); WRITE_UINT_FIELD(exclRelRTI); WRITE_NODE_FIELD(exclRelTlist); + WRITE_NODE_FIELD(fdwPartitionPrivLists); } static void *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 1562,1567 **** _readModifyTable(void) --- 1562,1568 ---- READ_BOOL_FIELD(canSetTag); READ_UINT_FIELD(nominalRelation); READ_NODE_FIELD(partitioned_rels); + READ_NODE_FIELD(partition_rels); READ_NODE_FIELD(resultRelations); READ_INT_FIELD(resultRelIndex); READ_INT_FIELD(rootResultRelIndex); *************** *** 1578,1583 **** _readModifyTable(void) --- 1579,1585 ---- READ_NODE_FIELD(onConflictWhere); READ_UINT_FIELD(exclRelRTI); READ_NODE_FIELD(exclRelTlist); + READ_NODE_FIELD(fdwPartitionPrivLists); READ_DONE(); } *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 35,40 **** --- 35,41 ---- #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" + #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/subselect.h" #include "optimizer/tlist.h" *************** *** 6412,6417 **** make_modifytable(PlannerInfo *root, --- 6413,6419 ---- ModifyTable *node = makeNode(ModifyTable); List *fdw_private_list; Bitmapset *direct_modify_plans; + List *partition_rels; ListCell *lc; int i; *************** *** 6535,6540 **** make_modifytable(PlannerInfo *root, --- 6537,6637 ---- node->fdwPrivLists = fdw_private_list; node->fdwDirectModifyPlans = direct_modify_plans; + /* + * Also, if this is an INSERT into a partitioned table, build a list of + * RT indexes of partitions, and for each partition that is a foreign table, + * allow the FDW to construct private plan data and accumulate it all into + * another list. + */ + partition_rels = NIL; + fdw_private_list = NIL; + if (operation == CMD_INSERT) + { + Index rti = linitial_int(resultRelations); + + if (planner_rt_fetch(rti, root)->relkind == RELKIND_PARTITIONED_TABLE) + { + Query *saved_query = root->parse; + Index saved_nominalRelation = node->nominalRelation; + List *saved_resultRelations = node->resultRelations; + List *saved_withCheckOptionLists = node->withCheckOptionLists; + List *saved_returningLists = node->returningLists; + Plan *subplan = (Plan *) linitial(node->plans); + List *saved_tlist = subplan->targetlist; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); + Index part_rti = appinfo->child_relid; + RangeTblEntry *part_rte; + FdwRoutine *fdwroutine = NULL; + List *fdw_private = NIL; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != rti) + continue; + + part_rte = planner_rt_fetch(part_rti, root); + Assert(part_rte->rtekind == RTE_RELATION); + + if (part_rte->relkind == RELKIND_FOREIGN_TABLE) + fdwroutine = GetFdwRoutineByRelId(part_rte->relid); + + if (fdwroutine != NULL && + fdwroutine->PlanForeignModify != NULL) + { + List *tlist; + + /* + * Replace the Query node with the modified one that has + * this partition as target. + */ + root->parse = (Query *) + adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Likewise for the ModifyTable node. + */ + node->nominalRelation = part_rti; + node->resultRelations = list_make1_int(part_rti); + node->withCheckOptionLists = + list_make1(root->parse->withCheckOptions); + node->returningLists = + list_make1(root->parse->returningList); + + /* + * Adjust the subplan's tlist because the column list of + * this partition might have a different column order + * and/or a different set of dropped columns than the + * partitioned table root. + */ + tlist = preprocess_targetlist(root, + root->parse->targetList); + subplan->targetlist = tlist; + + fdw_private = fdwroutine->PlanForeignModify(root, + node, + part_rti, + 0); + } + + partition_rels = lappend_int(partition_rels, part_rti); + fdw_private_list = lappend(fdw_private_list, fdw_private); + } + + root->parse = saved_query; + node->nominalRelation = saved_nominalRelation; + node->resultRelations = saved_resultRelations; + node->withCheckOptionLists = saved_withCheckOptionLists; + node->returningLists = saved_returningLists; + subplan->targetlist = saved_tlist; + } + } + node->partition_rels = partition_rels; + node->fdwPartitionPrivLists = fdw_private_list; + return node; } *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** *** 840,848 **** subquery_planner(PlannerGlobal *glob, Query *parse, /* * 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 --- 840,850 ---- /* * Do the main planning. If we have an inherited target relation, that ! * needs special processing except for INSERT cases, else go straight to ! * grouping_planner. */ ! if ((parse->commandType == CMD_UPDATE || ! parse->commandType == CMD_DELETE) && rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else *** a/src/backend/optimizer/plan/setrefs.c --- b/src/backend/optimizer/plan/setrefs.c *************** *** 850,855 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) --- 850,859 ---- { lfirst_int(l) += rtoffset; } + foreach(l, splan->partition_rels) + { + lfirst_int(l) += rtoffset; + } foreach(l, splan->resultRelations) { lfirst_int(l) += rtoffset; *** a/src/backend/optimizer/prep/preptlist.c --- b/src/backend/optimizer/prep/preptlist.c *************** *** 22,30 **** * The fact that rewriteTargetListIU sorts non-resjunk tlist entries by column * position, which expand_targetlist depends on, violates the above comment * because the sorting is only valid for the parent relation. In inherited ! * UPDATE cases, adjust_inherited_tlist runs in between to take care of fixing ! * the tlists for child tables to keep expand_targetlist happy. We do it like ! * that because it's faster in typical non-inherited cases. * * * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group --- 22,30 ---- * The fact that rewriteTargetListIU sorts non-resjunk tlist entries by column * position, which expand_targetlist depends on, violates the above comment * because the sorting is only valid for the parent relation. In inherited ! * INSERT/UPDATE cases, adjust_inherited_tlist runs in between to take care of ! * fixing the tlists for child tables to keep expand_targetlist happy. We do ! * it like that because it's faster in typical non-inherited cases. * * * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group *** a/src/backend/optimizer/prep/prepunion.c --- b/src/backend/optimizer/prep/prepunion.c *************** *** 1929,1936 **** adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, 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); --- 1929,1937 ---- if (newnode->resultRelation == appinfo->parent_relid) { newnode->resultRelation = appinfo->child_relid; ! /* Fix tlist resnos too, if it's inherited INSERT/UPDATE */ ! if (newnode->commandType == CMD_INSERT || ! newnode->commandType == CMD_UPDATE) newnode->targetList = adjust_inherited_tlist(newnode->targetList, appinfo); *************** *** 2227,2233 **** adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) } /* ! * Adjust the targetlist entries of an inherited UPDATE operation * * The expressions have already been fixed, but we have to make sure that * the target resnos match the child table (they may not, in the case of --- 2228,2234 ---- } /* ! * Adjust the targetlist entries of an inherited INSERT/UPDATE operation * * The expressions have already been fixed, but we have to make sure that * the target resnos match the child table (they may not, in the case of *************** *** 2239,2246 **** adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) * 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. */ static List * adjust_inherited_tlist(List *tlist, AppendRelInfo *context) --- 2240,2245 ---- *** a/src/backend/parser/analyze.c --- b/src/backend/parser/analyze.c *************** *** 542,547 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt) --- 542,554 ---- qry->resultRelation = setTargetTable(pstate, stmt->relation, false, false, targetPerms); + /* + * If the target table is a partitioned table, reset the inh flag to true. + */ + rte = pstate->p_target_rangetblentry; + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + rte->inh = true; + /* Validate stmt->cols list, or build default list if no list given */ icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos); Assert(list_length(icolumns) == list_length(attrnos)); *** a/src/backend/replication/logical/worker.c --- b/src/backend/replication/logical/worker.c *************** *** 198,204 **** create_estate_for_relation(LogicalRepRelMapEntry *rel) estate->es_range_table = list_make1(rte); resultRelInfo = makeNode(ResultRelInfo); ! InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0); estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; --- 198,204 ---- estate->es_range_table = list_make1(rte); resultRelInfo = makeNode(ResultRelInfo); ! InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0, 0); estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; *** a/src/backend/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** *** 2890,2902 **** rewriteTargetView(Query *parsetree, Relation view) new_rt_index = list_length(parsetree->rtable); /* - * INSERTs never inherit. For UPDATE/DELETE, we use the view query's - * inheritance flag for the base relation. - */ - if (parsetree->commandType == CMD_INSERT) - new_rte->inh = false; - - /* * Adjust the view's targetlist Vars to reference the new target RTE, ie * make their varnos be new_rt_index instead of base_rt_index. There can * be no Vars for other rels in the tlist, so this is sufficient to pull --- 2890,2895 ---- *** a/src/include/executor/executor.h --- b/src/include/executor/executor.h *************** *** 182,187 **** extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, --- 182,188 ---- Relation resultRelationDesc, Index resultRelationIndex, Relation partition_root, + Index partition_root_rtindex, int instrument_options); extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern void ExecCleanUpTriggerState(EState *estate); *************** *** 207,219 **** extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple); extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti); extern void ExecSetupPartitionTupleRouting(Relation rel, - Index resultRTindex, - EState *estate, PartitionDispatch **pd, ResultRelInfo **partitions, TupleConversionMap ***tup_conv_maps, TupleTableSlot **partition_tuple_slot, int *num_parted, int *num_partitions); extern int ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd, TupleTableSlot *slot, --- 208,225 ---- HeapTuple tuple); extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti); extern void ExecSetupPartitionTupleRouting(Relation rel, PartitionDispatch **pd, + List **leaf_parts, ResultRelInfo **partitions, TupleConversionMap ***tup_conv_maps, TupleTableSlot **partition_tuple_slot, int *num_parted, int *num_partitions); + extern void ExecInitPartition(EState *estate, + Oid partOid, + Index partRTindex, + ResultRelInfo *rootRelInfo, + ResultRelInfo *partRelInfo, + TupleConversionMap **partTupConvMap); extern int ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd, TupleTableSlot *slot, *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 412,417 **** typedef struct ResultRelInfo --- 412,423 ---- /* relation descriptor for root partitioned table */ Relation ri_PartitionRoot; + + /* range table index for root partitioned table */ + Index ri_PartitionRootRTindex; + + /* true when partition is legal for tuple-routing */ + bool ri_PartitionIsValid; } ResultRelInfo; /* ---------------- *** a/src/include/nodes/plannodes.h --- b/src/include/nodes/plannodes.h *************** *** 219,224 **** typedef struct ModifyTable --- 219,226 ---- Index nominalRelation; /* Parent RT index for use of EXPLAIN */ /* RT indexes of non-leaf tables in a partition tree */ List *partitioned_rels; + List *partition_rels; /* RT indexes of leaf tables in a partition + * tree */ 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 */ *************** *** 235,240 **** typedef struct ModifyTable --- 237,244 ---- Node *onConflictWhere; /* WHERE for ON CONFLICT UPDATE */ Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */ List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */ + List *fdwPartitionPrivLists; /* per-partition FDW private data + * lists */ } ModifyTable; /* ----------------