(2017/10/27 20:00), Etsuro Fujita wrote:
Please find attached an updated version of the patch.
Amit rebased this patch and sent me the rebased version off list.
Thanks for that, Amit!
One thing I noticed I overlooked is about this change I added to
make_modifytable to make a valid-looking plan node to pass to
PlanForeignModify to plan remote insert to each foreign partition:
+ /*
+ * 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);
This would be needed because the FDW might reference the tlist. Since
preprocess_targetlist references root->parse, it's needed to replace
that with the child query before calling that function, but I forgot to
do that. So I fixed that. Attached is an updated version of the patch.
Best regards,
Etsuro Fujita
*** 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');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! 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.
</para>
<sect3 id="ddl-partitioning-declarative-example">
--- 2998,3006 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<sect3 id="ddl-partitioning-declarative-example">
*** 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;
/* ----------------