*** a/contrib/file_fdw/input/file_fdw.source --- b/contrib/file_fdw/input/file_fdw.source *************** *** 176,182 **** COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ','); --- 176,188 ---- SELECT tableoid::regclass, * FROM pt; SELECT tableoid::regclass, * FROM p1; SELECT tableoid::regclass, * FROM p2; + \t on + EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (1, 'xyzzy'); + \t off INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR + \t on + EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (2, 'xyzzy'); + \t off INSERT INTO pt VALUES (2, 'xyzzy'); SELECT tableoid::regclass, * FROM pt; SELECT tableoid::regclass, * FROM p1; *** 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; *************** *** 341,348 **** SELECT tableoid::regclass, * FROM p2; p2 | 2 | qux (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 --- 341,366 ---- p2 | 2 | qux (2 rows) + \t on + EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (1, 'xyzzy'); + Insert on public.pt + Foreign Insert on public.p1 + Insert on public.p2 + -> Result + Output: 1, 'xyzzy'::text + + \t off INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR ! ERROR: cannot route inserted tuples to foreign table "p1" ! \t on ! EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (2, 'xyzzy'); ! Insert on public.pt ! Foreign Insert on public.p1 ! Insert on public.p2 ! -> Result ! Output: 2, 'xyzzy'::text ! ! \t off 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,7236 ---- drop table loct1; drop table loct2; -- =================================================================== + -- test tuple routing for 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; + -- Check INSERT into a multi-level partitioned table + create table mlpt (a int, b int, c varchar) partition by range (a); + create table mlptp1 partition of mlpt for values from (100) to (200) partition by range(b); + create table mlptp2 partition of mlpt for values from (200) to (300); + create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 100 and b < 200), c varchar); + create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 200 and b < 300), c varchar); + create foreign table mlptp1p1 partition of mlptp1 for values from (100) to (200) server loopback options (table_name 'locfoo'); + create foreign table mlptp1p2 partition of mlptp1 for values from (200) to (300) server loopback options (table_name 'locbar'); + explain (verbose, costs off) + insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *; + QUERY PLAN + ------------------------------------------------------------------------------------------ + Insert on public.mlpt + Output: mlpt.a, mlpt.b, mlpt.c + Foreign Insert on public.mlptp1p1 + Remote SQL: INSERT INTO public.locfoo(a, b, c) VALUES ($1, $2, $3) RETURNING a, b, c + Foreign Insert on public.mlptp1p2 + Remote SQL: INSERT INTO public.locbar(a, b, c) VALUES ($1, $2, $3) RETURNING a, b, c + Insert on public.mlptp2 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2, "*VALUES*".column3 + (9 rows) + + insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *; + a | b | c + -----+-----+--- + 101 | 101 | x + 101 | 201 | y + (2 rows) + + select tableoid::regclass, * FROM mlpt; + tableoid | a | b | c + ----------+-----+-----+--- + mlptp1p1 | 101 | 101 | x + mlptp1p2 | 101 | 201 | y + (2 rows) + + select tableoid::regclass, * FROM mlptp1; + tableoid | a | b | c + ----------+-----+-----+--- + mlptp1p1 | 101 | 101 | x + mlptp1p2 | 101 | 201 | y + (2 rows) + + select tableoid::regclass, * FROM mlptp2; + tableoid | a | b | c + ----------+---+---+--- + (0 rows) + + select tableoid::regclass, * FROM mlptp1p1; + tableoid | a | b | c + ----------+-----+-----+--- + mlptp1p1 | 101 | 101 | x + (1 row) + + select tableoid::regclass, * FROM mlptp1p2; + tableoid | a | b | c + ----------+-----+-----+--- + mlptp1p2 | 101 | 201 | y + (1 row) + + drop table mlpt; + drop table locfoo; + 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,1730 ---- drop table loct2; -- =================================================================== + -- test tuple routing for 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; + + -- Check INSERT into a multi-level partitioned table + create table mlpt (a int, b int, c varchar) partition by range (a); + create table mlptp1 partition of mlpt for values from (100) to (200) partition by range(b); + create table mlptp2 partition of mlpt for values from (200) to (300); + create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 100 and b < 200), c varchar); + create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 200 and b < 300), c varchar); + create foreign table mlptp1p1 partition of mlptp1 for values from (100) to (200) server loopback options (table_name 'locfoo'); + create foreign table mlptp1p2 partition of mlptp1 for values from (200) to (300) server loopback options (table_name 'locbar'); + + explain (verbose, costs off) + insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *; + insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *; + + select tableoid::regclass, * FROM mlpt; + select tableoid::regclass, * FROM mlptp1; + select tableoid::regclass, * FROM mlptp2; + select tableoid::regclass, * FROM mlptp1p1; + select tableoid::regclass, * FROM mlptp1p2; + + drop table mlpt; + drop table locfoo; + drop table locbar; + + -- =================================================================== -- test IMPORT FOREIGN SCHEMA -- =================================================================== *** a/doc/src/sgml/ddl.sgml --- b/doc/src/sgml/ddl.sgml *************** *** 2998,3008 **** VALUES ('Albany', NULL, NULL, 'NY'); ! Partitions can also be foreign tables ! (see ), ! although these have some limitations that normal tables do not. For ! example, data inserted into the partitioned table is not routed to ! foreign table partitions. --- 2998,3006 ---- ! Partitions can also be foreign tables, although they have some limitations ! that normal tables do not; see for ! more information. *** a/src/backend/commands/copy.c --- b/src/backend/commands/copy.c *************** *** 2472,2487 **** 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, --- 2472,2490 ---- 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, *************** *** 2493,2498 **** CopyFrom(CopyState cstate) --- 2496,2535 ---- cstate->partition_tupconv_maps = partition_tupconv_maps; cstate->partition_tuple_slot = partition_tuple_slot; + partRelInfo = (ResultRelInfo *) palloc0(num_partitions * + sizeof(ResultRelInfo)); + i = 0; + foreach(l, partition_oids) + { + Oid partOid = lfirst_oid(l); + Relation partrel; + + /* Prepare ResultRelInfo and map for the partition */ + ExecInitPartition(estate, + resultRelInfo, + partOid, + 0, /* dummy rangetable index */ + partRelInfo, + &partition_tupconv_maps[i]); + partitions[i] = partRelInfo; + + /* 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 + { + /* The partition 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 *************** *** 2641,2651 **** 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 --- 2678,2693 ---- saved_resultRelInfo = resultRelInfo; resultRelInfo = cstate->partitions[leaf_part_index]; ! if (!resultRelInfo->ri_PartitionIsValid) ! { ! /* The partition should be foreign */ ! Assert(resultRelInfo->ri_FdwRoutine); ! ! /* We do not yet have a way to copy into a foreign partition */ 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 *************** *** 117,122 **** static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es); --- 117,126 ---- 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, + const char *operation, bool labeltarget, + bool main_target, int subplan_index, ExplainState *es); static void ExplainMemberNodes(List *plans, PlanState **planstates, List *ancestors, ExplainState *es); static void ExplainSubPlans(List *plans, List *ancestors, *************** *** 829,834 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) --- 833,849 ---- 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; *************** *** 2889,2945 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors, 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 */ --- 2904,2930 ---- ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j; FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; ! show_actual_target(mtstate, node, resultRelInfo, fdwroutine, ! fdwroutine ? foperation : operation, ! labeltargets, true, j, es); ! } ! /* Print partition tables if needed */ ! if (mtstate->mt_num_partitions > 0) ! { ! ExplainOpenGroup("Partition Tables", "Partition Tables", false, es); ! for (j = 0; j < mtstate->mt_num_partitions; j++) { ! ResultRelInfo *resultRelInfo = mtstate->mt_partitions[j]; ! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; ! show_actual_target(mtstate, node, resultRelInfo, fdwroutine, ! fdwroutine ? foperation : operation, ! true, false, j, es); } ! ExplainCloseGroup("Partition Tables", "Partition Tables", false, es); } /* Gather names of ON CONFLICT arbiter indexes */ *************** *** 2996,3001 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors, --- 2981,3058 ---- } /* + * Show an actual target relation + */ + static void + show_actual_target(ModifyTableState *mtstate, ModifyTable *node, + ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine, + const char *operation, bool labeltarget, + bool main_target, int subplan_index, ExplainState *es) + { + if (labeltarget) + { + /* Open a group for this target */ + if (main_target) + ExplainOpenGroup("Target Table", NULL, true, es); + else + ExplainOpenGroup("Partition 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 */ + if (main_target) + ExplainCloseGroup("Target Table", NULL, true, es); + else + ExplainCloseGroup("Partition 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 *************** *** 1100,1111 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation) --- 1100,1114 ---- 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, *************** *** 1172,1195 **** 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) --- 1175,1202 ---- 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) *************** *** 1306,1312 **** void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, ! Relation partition_root, int instrument_options) { List *partition_check = NIL; --- 1313,1319 ---- InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, ! ResultRelInfo *partition_root, int instrument_options) { List *partition_check = NIL; *************** *** 1362,1369 **** InitResultRelInfo(ResultRelInfo *resultRelInfo, */ partition_check = RelationGetPartitionQual(resultRelationDesc); - resultRelInfo->ri_PartitionCheck = partition_check; resultRelInfo->ri_PartitionRoot = partition_root; } /* --- 1369,1377 ---- */ partition_check = RelationGetPartitionQual(resultRelationDesc); resultRelInfo->ri_PartitionRoot = partition_root; + resultRelInfo->ri_PartitionCheck = partition_check; + resultRelInfo->ri_PartitionIsValid = false; } /* *************** *** 1890,1904 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, { char *val_desc; Relation orig_rel = rel; /* See the comment above. */ ! if (resultRelInfo->ri_PartitionRoot) { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleDesc old_tupdesc = RelationGetDescr(rel); TupleConversionMap *map; ! rel = resultRelInfo->ri_PartitionRoot; tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(old_tupdesc, tupdesc, --- 1898,1913 ---- { char *val_desc; Relation orig_rel = rel; + ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot; /* See the comment above. */ ! if (rootRelInfo) { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleDesc old_tupdesc = RelationGetDescr(rel); TupleConversionMap *map; ! rel = rootRelInfo->ri_RelationDesc; tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(old_tupdesc, tupdesc, *************** *** 1909,1918 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, ExecSetSlotDescriptor(slot, tupdesc); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } - } ! insertedCols = GetInsertedColumns(resultRelInfo, estate); ! updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, --- 1918,1932 ---- ExecSetSlotDescriptor(slot, tupdesc); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } ! insertedCols = GetInsertedColumns(rootRelInfo, estate); ! updatedCols = GetUpdatedColumns(rootRelInfo, estate); ! } ! else ! { ! insertedCols = GetInsertedColumns(resultRelInfo, estate); ! updatedCols = GetUpdatedColumns(resultRelInfo, estate); ! } modifiedCols = bms_union(insertedCols, updatedCols); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, *************** *** 1964,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo, --- 1978,1984 ---- char *val_desc; Relation orig_rel = rel; TupleDesc orig_tupdesc = RelationGetDescr(rel); + ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot; /* * If the tuple has been routed, it's been converted to the *************** *** 1972,1983 **** ExecConstraints(ResultRelInfo *resultRelInfo, * rowtype so that val_desc shown error message matches the * input tuple. */ ! if (resultRelInfo->ri_PartitionRoot) { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleConversionMap *map; ! rel = resultRelInfo->ri_PartitionRoot; tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(orig_tupdesc, tupdesc, --- 1987,1998 ---- * rowtype so that val_desc shown error message matches the * input tuple. */ ! if (rootRelInfo) { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleConversionMap *map; ! rel = rootRelInfo->ri_RelationDesc; tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(orig_tupdesc, tupdesc, *************** *** 1988,1997 **** ExecConstraints(ResultRelInfo *resultRelInfo, ExecSetSlotDescriptor(slot, tupdesc); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } - } ! insertedCols = GetInsertedColumns(resultRelInfo, estate); ! updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, --- 2003,2017 ---- ExecSetSlotDescriptor(slot, tupdesc); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } ! insertedCols = GetInsertedColumns(rootRelInfo, estate); ! updatedCols = GetUpdatedColumns(rootRelInfo, estate); ! } ! else ! { ! insertedCols = GetInsertedColumns(resultRelInfo, estate); ! updatedCols = GetUpdatedColumns(resultRelInfo, estate); ! } modifiedCols = bms_union(insertedCols, updatedCols); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, *************** *** 2017,2031 **** ExecConstraints(ResultRelInfo *resultRelInfo, { char *val_desc; Relation orig_rel = rel; /* See the comment above. */ ! if (resultRelInfo->ri_PartitionRoot) { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleDesc old_tupdesc = RelationGetDescr(rel); TupleConversionMap *map; ! rel = resultRelInfo->ri_PartitionRoot; tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(old_tupdesc, tupdesc, --- 2037,2052 ---- { char *val_desc; Relation orig_rel = rel; + ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot; /* See the comment above. */ ! if (rootRelInfo) { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleDesc old_tupdesc = RelationGetDescr(rel); TupleConversionMap *map; ! rel = rootRelInfo->ri_RelationDesc; tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(old_tupdesc, tupdesc, *************** *** 2036,2045 **** ExecConstraints(ResultRelInfo *resultRelInfo, ExecSetSlotDescriptor(slot, tupdesc); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } - } ! insertedCols = GetInsertedColumns(resultRelInfo, estate); ! updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, --- 2057,2071 ---- ExecSetSlotDescriptor(slot, tupdesc); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } ! insertedCols = GetInsertedColumns(rootRelInfo, estate); ! updatedCols = GetUpdatedColumns(rootRelInfo, estate); ! } ! else ! { ! insertedCols = GetInsertedColumns(resultRelInfo, estate); ! updatedCols = GetUpdatedColumns(resultRelInfo, estate); ! } modifiedCols = bms_union(insertedCols, updatedCols); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, *************** *** 2111,2116 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, --- 2137,2143 ---- */ if (!ExecQual(wcoExpr, econtext)) { + ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot; char *val_desc; Bitmapset *modifiedCols; Bitmapset *insertedCols; *************** *** 2129,2141 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, */ case WCO_VIEW_CHECK: /* See the comment in ExecConstraints(). */ ! if (resultRelInfo->ri_PartitionRoot) { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleDesc old_tupdesc = RelationGetDescr(rel); TupleConversionMap *map; ! rel = resultRelInfo->ri_PartitionRoot; tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(old_tupdesc, tupdesc, --- 2156,2168 ---- */ case WCO_VIEW_CHECK: /* See the comment in ExecConstraints(). */ ! if (rootRelInfo) { HeapTuple tuple = ExecFetchSlotTuple(slot); TupleDesc old_tupdesc = RelationGetDescr(rel); TupleConversionMap *map; ! rel = rootRelInfo->ri_RelationDesc; tupdesc = RelationGetDescr(rel); /* a reverse map */ map = convert_tuples_by_name(old_tupdesc, tupdesc, *************** *** 2146,2155 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, ExecSetSlotDescriptor(slot, tupdesc); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } - } ! insertedCols = GetInsertedColumns(resultRelInfo, estate); ! updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, --- 2173,2187 ---- ExecSetSlotDescriptor(slot, tupdesc); ExecStoreTuple(tuple, slot, InvalidBuffer, false); } ! insertedCols = GetInsertedColumns(rootRelInfo, estate); ! updatedCols = GetUpdatedColumns(rootRelInfo, estate); ! } ! else ! { ! insertedCols = GetInsertedColumns(resultRelInfo, estate); ! updatedCols = GetUpdatedColumns(resultRelInfo, estate); ! } modifiedCols = bms_union(insertedCols, updatedCols); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, *** a/src/backend/executor/execPartition.c --- b/src/backend/executor/execPartition.c *************** *** 44,49 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel, --- 44,51 ---- * 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 *************** *** 64,90 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel, */ 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 * --- 66,85 ---- */ 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 * *************** *** 97,152 **** ExecSetupPartitionTupleRouting(Relation rel, * processing. */ *partition_tuple_slot = MakeTupleTableSlot(); ! leaf_part_rri = (ResultRelInfo *) palloc0(*num_partitions * ! sizeof(ResultRelInfo)); ! 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); ! (*partitions)[i] = leaf_part_rri++; ! i++; ! } } /* --- 92,148 ---- * processing. */ *partition_tuple_slot = MakeTupleTableSlot(); + } ! /* ! * ExecInitPartition -- Prepare ResultRelInfo and tuple conversion map for ! * the partition with OID 'partOid' ! */ ! void ! ExecInitPartition(EState *estate, ! ResultRelInfo *rootRelInfo, ! Oid partOid, ! Index partRTindex, ! 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 ResultRelInfo data for the partition. */ ! InitResultRelInfo(partRelInfo, ! partrel, ! partRTindex, ! rootRelInfo, ! 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); ! /* Store ResultRelInfo in *estate. */ ! estate->es_leaf_result_relations = ! lappend(estate->es_leaf_result_relations, partRelInfo); ! /* ! * Save 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")); } /* *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 306,316 **** 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; --- 306,322 ---- saved_resultRelInfo = resultRelInfo; resultRelInfo = mtstate->mt_partitions[leaf_part_index]; ! if (!resultRelInfo->ri_PartitionIsValid) ! { ! /* The partition 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; *************** *** 1946,1961 **** 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, --- 1952,1968 ---- 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; ExecSetupPartitionTupleRouting(rel, &partition_dispatch_info, + &partition_oids, &partitions, &partition_tupconv_maps, &partition_tuple_slot, *************** *** 1966,1971 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) --- 1973,2016 ---- mtstate->mt_num_partitions = num_partitions; mtstate->mt_partition_tupconv_maps = partition_tupconv_maps; mtstate->mt_partition_tuple_slot = partition_tuple_slot; + + partRelInfo = (ResultRelInfo *) palloc0(num_partitions * + sizeof(ResultRelInfo)); + i = 0; + foreach(l, partition_oids) + { + Oid partOid = lfirst_oid(l); + Index partRTindex = list_nth_int(node->partition_rels, i); + + /* Prepare ResultRelInfo and map for the partition */ + ExecInitPartition(estate, + mtstate->resultRelInfo, + partOid, + partRTindex, + partRelInfo, + &partition_tupconv_maps[i]); + partitions[i] = partRelInfo; + + /* Verify the partition is a valid target for INSERT */ + CheckValidResultRel(partRelInfo, CMD_INSERT); + + /* If so, allow the FDW to 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, i); + + partRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate, + partRelInfo, + fdw_private, + 0, + eflags); + } + + partRelInfo++; + i++; + } } /* *************** *** 2391,2396 **** ExecEndModifyTable(ModifyTableState *node) --- 2436,2446 ---- { 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 *************** *** 372,377 **** _outModifyTable(StringInfo str, const ModifyTable *node) --- 372,378 ---- 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); *************** *** 388,393 **** _outModifyTable(StringInfo str, const ModifyTable *node) --- 389,395 ---- 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 *************** *** 1568,1573 **** _readModifyTable(void) --- 1568,1574 ---- 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); *************** *** 1584,1589 **** _readModifyTable(void) --- 1585,1591 ---- 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" *************** *** 6435,6440 **** make_modifytable(PlannerInfo *root, --- 6436,6442 ---- ModifyTable *node = makeNode(ModifyTable); List *fdw_private_list; Bitmapset *direct_modify_plans; + List *partition_rels; ListCell *lc; int i; *************** *** 6558,6563 **** make_modifytable(PlannerInfo *root, --- 6560,6695 ---- 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 foreign partition, allow the FDW + * to construct private plan data and accumulate it all into another list. + * + * Note: ExecSetupPartitionTupleRouting() will expand partitions in the + * same order as these lists. + */ + partition_rels = NIL; + fdw_private_list = NIL; + if (operation == CMD_INSERT && + planner_rt_fetch(nominalRelation, root)->relkind == RELKIND_PARTITIONED_TABLE) + { + List *saved_withCheckOptionLists = node->withCheckOptionLists; + List *saved_returningLists = node->returningLists; + Plan *subplan = (Plan *) linitial(node->plans); + List *saved_tlist = subplan->targetlist; + Query **parent_parses; + Query *parent_parse; + Bitmapset *parent_relids; + + /* + * Similarly to inheritance_planner(), we generate a modified query + * with each child as target by applying adjust_appendrel_attrs() to + * the query for its immediate parent, so build an array to store in + * the query for each parent. + */ + parent_parses = (Query **) + palloc0((list_length(root->parse->rtable) + 1) * sizeof(Query *)); + + parent_parses[nominalRelation] = root->parse; + parent_relids = bms_make_singleton(nominalRelation); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + Index parent_rti = appinfo->parent_relid; + Index child_rti = appinfo->child_relid; + RangeTblEntry *child_rte; + Query *child_parse; + FdwRoutine *fdwroutine = NULL; + List *fdw_private = NIL; + + /* append_rel_list contains all append rels; ignore others */ + if (!bms_is_member(parent_rti, parent_relids)) + continue; + + child_rte = planner_rt_fetch(child_rti, root); + Assert(child_rte->rtekind == RTE_RELATION); + + /* No work if the child is a plain table */ + if (child_rte->relkind == RELKIND_RELATION) + { + partition_rels = lappend_int(partition_rels, child_rti); + fdw_private_list = lappend(fdw_private_list, NIL); + continue; + } + + /* + * expand_inherited_rtentry() always processes a parent before any + * of that parent's children, so the query for its parent should + * already be available. + */ + parent_parse = parent_parses[parent_rti]; + Assert(parent_parse); + + /* Generate the query with the child as target */ + child_parse = (Query *) + adjust_appendrel_attrs(root, + (Node *) parent_parse, + 1, &appinfo); + + /* Store the query if the child is a partitioned table */ + if (child_rte->relkind == RELKIND_PARTITIONED_TABLE) + { + parent_parses[child_rti] = child_parse; + parent_relids = bms_add_member(parent_relids, child_rti); + continue; + } + + /* The child should be a foreign table */ + Assert(child_rte->relkind == RELKIND_FOREIGN_TABLE); + + fdwroutine = GetFdwRoutineByRelId(child_rte->relid); + Assert(fdwroutine != NULL); + + if (fdwroutine->PlanForeignModify != NULL) + { + List *tlist; + + /* Replace the root query with the child query. */ + root->parse = child_parse; + + /* Adjust the plan node to refer to the child as target. */ + node->nominalRelation = child_rti; + node->resultRelations = list_make1_int(child_rti); + node->withCheckOptionLists = + list_make1(child_parse->withCheckOptions); + node->returningLists = + list_make1(child_parse->returningList); + + /* + * The column list of the child might have a different column + * order and/or a different set of dropped columns than that + * of its parent, so adjust the subplan's tlist as well. + */ + tlist = preprocess_targetlist(root, + child_parse->targetList); + subplan->targetlist = tlist; + + fdw_private = fdwroutine->PlanForeignModify(root, + node, + child_rti, + 0); + + subplan->targetlist = saved_tlist; + node->nominalRelation = nominalRelation; + node->resultRelations = list_make1_int(nominalRelation); + node->withCheckOptionLists = saved_withCheckOptionLists; + node->returningLists = saved_returningLists; + root->parse = parent_parses[nominalRelation]; + } + + partition_rels = lappend_int(partition_rels, child_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 *************** *** 879,888 **** subquery_planner(PlannerGlobal *glob, Query *parse, reduce_outer_joins(root); /* ! * Do the main planning. If we have an inherited target relation, that ! * needs special processing, else go straight to grouping_planner. */ ! if (parse->resultRelation && rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else --- 879,890 ---- reduce_outer_joins(root); /* ! * Do the main planning. If we have an inherited UPDATE/DELETE 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 *************** *** 858,863 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) --- 858,867 ---- { 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/prepunion.c --- b/src/backend/optimizer/prep/prepunion.c *************** *** 1974,1981 **** 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); --- 1974,1982 ---- 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); *************** *** 2325,2331 **** adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, } /* ! * 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 --- 2326,2332 ---- } /* ! * 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 *************** *** 2337,2344 **** adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, * 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) --- 2338,2343 ---- *** a/src/backend/parser/analyze.c --- b/src/backend/parser/analyze.c *************** *** 542,547 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt) --- 542,552 ---- qry->resultRelation = setTargetTable(pstate, stmt->relation, false, false, targetPerms); + /* Set the inh flag to true if the target table is partitioned */ + 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 *************** *** 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/execPartition.h --- b/src/include/executor/execPartition.h *************** *** 50,62 **** typedef struct PartitionDispatchData typedef struct PartitionDispatchData *PartitionDispatch; 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, --- 50,67 ---- typedef struct PartitionDispatchData *PartitionDispatch; 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, + ResultRelInfo *rootRelInfo, + Oid partOid, + Index partRTindex, + ResultRelInfo *partRelInfo, + TupleConversionMap **partTupConvMap); extern int ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd, TupleTableSlot *slot, *** a/src/include/executor/executor.h --- b/src/include/executor/executor.h *************** *** 181,187 **** extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation) extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, ! Relation partition_root, int instrument_options); extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern void ExecCleanUpTriggerState(EState *estate); --- 181,187 ---- extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, ! ResultRelInfo *partition_root, int instrument_options); extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern void ExecCleanUpTriggerState(EState *estate); *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 404,417 **** typedef struct ResultRelInfo /* list of ON CONFLICT DO UPDATE exprs (qual) */ ExprState *ri_onConflictSetWhere; /* partition check expression */ List *ri_PartitionCheck; /* partition check expression state */ ExprState *ri_PartitionCheckExpr; ! /* relation descriptor for root partitioned table */ ! Relation ri_PartitionRoot; } ResultRelInfo; /* ---------------- --- 404,420 ---- /* list of ON CONFLICT DO UPDATE exprs (qual) */ ExprState *ri_onConflictSetWhere; + /* root partitioned table */ + struct ResultRelInfo *ri_PartitionRoot; + /* partition check expression */ List *ri_PartitionCheck; /* partition check expression state */ ExprState *ri_PartitionCheckExpr; ! /* 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; /* ----------------