*** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** *** 6931,6936 **** NOTICE: drop cascades to foreign table bar2 --- 6931,7074 ---- 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 *************** *** 1615,1620 **** drop table loct1; --- 1615,1660 ---- 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/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, *************** *** 821,826 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) --- 825,841 ---- 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; *************** *** 2792,2853 **** 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); ! } } /* Gather names of ON CONFLICT arbiter indexes */ --- 2807,2835 ---- 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++) ! { ! /* ! * We arrange mt_partitions in the plan's partition_rels list order, ! * which is the same order as for UPDATE/DELETE cases. ! */ ! ResultRelInfo *resultRelInfo = mtstate->mt_partitions + mtstate->mt_partition_indexes[j]; ! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; ! show_actual_target(mtstate, node, resultRelInfo, fdwroutine, false, j, ! fdwroutine ? foperation : operation, true, es); } /* Gather names of ON CONFLICT arbiter indexes */ *************** *** 2904,2909 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors, --- 2886,2957 ---- } /* + * 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/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 92,97 **** static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid, --- 92,99 ---- Bitmapset *modifiedCols, AclMode requiredPerms); static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt); + static ResultRelInfo *ExecFindResultRelInfo(EState *estate, Oid relid, + bool missing_ok); static char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc tupdesc, *************** *** 1360,1365 **** InitResultRelInfo(ResultRelInfo *resultRelInfo, --- 1362,1392 ---- } /* + * ExecFindResultRelInfo -- find the ResultRelInfo struct for given relation OID + * + * If no such struct, either return NULL or throw error depending on missing_ok + */ + static ResultRelInfo * + ExecFindResultRelInfo(EState *estate, Oid relid, bool missing_ok) + { + ResultRelInfo *rInfo; + int nr; + + rInfo = estate->es_result_relations; + nr = estate->es_num_result_relations; + while (nr > 0) + { + if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) + return rInfo; + rInfo++; + nr--; + } + if (!missing_ok) + elog(ERROR, "failed to find ResultRelInfo for relation %u", relid); + return NULL; + } + + /* * ExecGetTriggerResultRel * * Get a ResultRelInfo for a trigger target relation. Most of the time, *************** *** 1379,1399 **** ResultRelInfo * ExecGetTriggerResultRel(EState *estate, Oid relid) { ResultRelInfo *rInfo; - int nr; ListCell *l; Relation rel; MemoryContext oldcontext; /* First, search through the query result relations */ ! rInfo = estate->es_result_relations; ! nr = estate->es_num_result_relations; ! while (nr > 0) ! { ! if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) ! return rInfo; ! rInfo++; ! nr--; ! } /* Nope, but maybe we already made an extra ResultRelInfo for it */ foreach(l, estate->es_trig_target_relations) { --- 1406,1419 ---- ExecGetTriggerResultRel(EState *estate, Oid relid) { ResultRelInfo *rInfo; ListCell *l; Relation rel; MemoryContext oldcontext; /* First, search through the query result relations */ ! rInfo = ExecFindResultRelInfo(estate, relid, true); ! if (rInfo) ! return rInfo; /* Nope, but maybe we already made an extra ResultRelInfo for it */ foreach(l, estate->es_trig_target_relations) { *************** *** 1828,1833 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, --- 1848,1854 ---- EState *estate) { Relation rel = resultRelInfo->ri_RelationDesc; + Oid relid = RelationGetRelid(rel); TupleDesc tupdesc = RelationGetDescr(rel); Bitmapset *modifiedCols; Bitmapset *insertedCols; *************** *** 1870,1877 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, --- 1891,1900 ---- HeapTuple tuple = ExecFetchSlotTuple(slot); TupleDesc old_tupdesc = RelationGetDescr(rel); TupleConversionMap *map; + ResultRelInfo *rInfo; rel = resultRelInfo->ri_PartitionRoot; + relid = RelationGetRelid(rel); tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(old_tupdesc, tupdesc, *************** *** 1881,1892 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, tuple = do_convert_tuple(tuple, map); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } } - - insertedCols = GetInsertedColumns(resultRelInfo, estate); - updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); ! val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, --- 1904,1921 ---- tuple = do_convert_tuple(tuple, map); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } + /* Find the root table's ResultRelInfo */ + rInfo = ExecFindResultRelInfo(estate, relid, false); + insertedCols = GetInsertedColumns(rInfo, estate); + updatedCols = GetUpdatedColumns(rInfo, estate); + } + else + { + insertedCols = GetInsertedColumns(resultRelInfo, estate); + updatedCols = GetUpdatedColumns(resultRelInfo, estate); } modifiedCols = bms_union(insertedCols, updatedCols); ! val_desc = ExecBuildSlotValueDescription(relid, slot, tupdesc, modifiedCols, *************** *** 1914,1919 **** ExecConstraints(ResultRelInfo *resultRelInfo, --- 1943,1949 ---- TupleTableSlot *slot, EState *estate) { Relation rel = resultRelInfo->ri_RelationDesc; + Oid relid = RelationGetRelid(rel); TupleDesc tupdesc = RelationGetDescr(rel); TupleConstr *constr = tupdesc->constr; Bitmapset *modifiedCols; *************** *** 1947,1954 **** ExecConstraints(ResultRelInfo *resultRelInfo, --- 1977,1986 ---- { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleConversionMap *map; + ResultRelInfo *rInfo; rel = resultRelInfo->ri_PartitionRoot; + relid = RelationGetRelid(rel); tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(orig_tupdesc, tupdesc, *************** *** 1958,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo, tuple = do_convert_tuple(tuple, map); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } } - - insertedCols = GetInsertedColumns(resultRelInfo, estate); - updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); ! val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, --- 1990,2007 ---- tuple = do_convert_tuple(tuple, map); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } + /* Find the root table's ResultRelInfo */ + rInfo = ExecFindResultRelInfo(estate, relid, false); + insertedCols = GetInsertedColumns(rInfo, estate); + updatedCols = GetUpdatedColumns(rInfo, estate); + } + else + { + insertedCols = GetInsertedColumns(resultRelInfo, estate); + updatedCols = GetUpdatedColumns(resultRelInfo, estate); } modifiedCols = bms_union(insertedCols, updatedCols); ! val_desc = ExecBuildSlotValueDescription(relid, slot, tupdesc, modifiedCols, *************** *** 1987,1992 **** ExecConstraints(ResultRelInfo *resultRelInfo, --- 2025,2031 ---- { char *val_desc; Relation orig_rel = rel; + Oid relid = RelationGetRelid(rel); /* See the comment above. */ if (resultRelInfo->ri_PartitionRoot) *************** *** 1994,2001 **** ExecConstraints(ResultRelInfo *resultRelInfo, --- 2033,2042 ---- HeapTuple tuple = ExecFetchSlotTuple(slot); TupleDesc old_tupdesc = RelationGetDescr(rel); TupleConversionMap *map; + ResultRelInfo *rInfo; rel = resultRelInfo->ri_PartitionRoot; + relid = RelationGetRelid(rel); tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(old_tupdesc, tupdesc, *************** *** 2005,2016 **** ExecConstraints(ResultRelInfo *resultRelInfo, tuple = do_convert_tuple(tuple, map); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } } - - insertedCols = GetInsertedColumns(resultRelInfo, estate); - updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); ! val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, --- 2046,2063 ---- tuple = do_convert_tuple(tuple, map); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } + /* Find the root table's ResultRelInfo */ + rInfo = ExecFindResultRelInfo(estate, relid, false); + insertedCols = GetInsertedColumns(rInfo, estate); + updatedCols = GetUpdatedColumns(rInfo, estate); + } + else + { + insertedCols = GetInsertedColumns(resultRelInfo, estate); + updatedCols = GetUpdatedColumns(resultRelInfo, estate); } modifiedCols = bms_union(insertedCols, updatedCols); ! val_desc = ExecBuildSlotValueDescription(relid, slot, tupdesc, modifiedCols, *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 304,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; --- 304,309 ---- *************** *** 1925,1930 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) --- 1919,1970 ---- mtstate->mt_num_partitions = num_partitions; mtstate->mt_partition_tupconv_maps = partition_tupconv_maps; mtstate->mt_partition_tuple_slot = partition_tuple_slot; + + mtstate->mt_partition_indexes = (int *) + palloc0(num_partitions * sizeof(int)); + i = 0; + foreach(l, node->partition_rels) + { + Index rti = lfirst_int(l); + Oid relid = getrelid(rti, estate->es_range_table); + bool found; + int j; + + /* First, find the ResultRelInfo for the partition */ + found = false; + for (j = 0; j < mtstate->mt_num_partitions; j++) + { + resultRelInfo = partitions + j; + + if (RelationGetRelid(resultRelInfo->ri_RelationDesc) == relid) + { + Assert(mtstate->mt_partition_indexes[i] == 0); + mtstate->mt_partition_indexes[i] = j; + found = true; + break; + } + } + if (!found) + elog(ERROR, "failed to find ResultRelInfo for rangetable index %u", rti); + + /* Fix the rangetable index of the ResultRelInfo */ + resultRelInfo->ri_RangeTableIndex = rti; + + /* Let FDWs init themselves for foreign-table result rels */ + if (resultRelInfo->ri_FdwRoutine != NULL && + resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL) + { + List *fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, i); + + resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate, + resultRelInfo, + fdw_private, + 0, + eflags); + } + + i++; + } } /* Build state for collecting transition tuples */ *************** *** 2345,2350 **** ExecEndModifyTable(ModifyTableState *node) --- 2385,2395 ---- { 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 *************** *** 349,354 **** _outModifyTable(StringInfo str, const ModifyTable *node) --- 349,355 ---- 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); *************** *** 365,370 **** _outModifyTable(StringInfo str, const ModifyTable *node) --- 366,372 ---- 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 *************** *** 1547,1552 **** _readModifyTable(void) --- 1547,1553 ---- 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); *************** *** 1563,1568 **** _readModifyTable(void) --- 1564,1570 ---- 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" *************** *** 6405,6410 **** make_modifytable(PlannerInfo *root, --- 6406,6412 ---- ModifyTable *node = makeNode(ModifyTable); List *fdw_private_list; Bitmapset *direct_modify_plans; + List *partition_rels; ListCell *lc; int i; *************** *** 6525,6530 **** make_modifytable(PlannerInfo *root, --- 6527,6600 ---- 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) + { + 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) + { + PlannerInfo *part_root; + ModifyTable *part_node; + + /* Create a working copy of the PlannerInfo */ + part_root = makeNode(PlannerInfo); + memcpy(part_root, root, sizeof(PlannerInfo)); + /* Generate modified query with this partition as target */ + part_root->parse = (Query *) + adjust_appendrel_attrs(root, + (Node *) root->parse, + appinfo); + + /* Create a working copy of the ModifyTable */ + part_node = copyObject(node); + part_node->nominalRelation = part_rti; + part_node->resultRelations = list_make1_int(part_rti); + part_node->returningLists = + list_make1(part_root->parse->returningList); + + fdw_private = fdwroutine->PlanForeignModify(part_root, + part_node, + part_rti, + 0); + } + + partition_rels = lappend_int(partition_rels, part_rti); + fdw_private_list = lappend(fdw_private_list, fdw_private); + } + } + } + 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 *************** *** 828,834 **** 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 --- 828,835 ---- * Do the main planning. If we have an inherited target relation, that * needs special processing, 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/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/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** *** 2859,2871 **** 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 --- 2859,2864 ---- *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 963,968 **** typedef struct ModifyTableState --- 963,970 ---- TupleConversionMap **mt_partition_tupconv_maps; /* Per partition tuple conversion map */ TupleTableSlot *mt_partition_tuple_slot; + int *mt_partition_indexes; /* indexes into mt_partitions for use + * in explain.c */ struct TransitionCaptureState *mt_transition_capture; /* controls transition table population */ TupleConversionMap **mt_transition_tupconv_maps; *** 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; /* ----------------