Hi Maksim,
On 2017/10/02 21:37, Maksim Milyutin wrote:
On 11.09.2017 16:01, Etsuro Fujita wrote:
* Query planning: the patch creates copies of Query/Plan with a
foreign partition as target from the original Query/Plan for each
foreign partition and invokes PlanForeignModify with those copies, to
allow the FDW to do query planning for remote INSERT with the existing
API. To make such Queries the similar way inheritance_planner does, I
modified transformInsertStmt so that the inh flag for the partitioned
table's RTE is set to true, which allows (1) expand_inherited_rtentry
to build an RTE and AppendRelInfo for each partition in the
partitioned table and (2) make_modifytable to build such Queries using
adjust_appendrel_attrs and those AppendRelInfos.
* explain.c: I modified show_modifytable_info so that we can show
remote queries for foreign partitions in EXPLAIN for INSERT into a
partitioned table the same way as for inherited UPDATE/DELETE cases.
Here is an example:
postgres=# explain verbose insert into pt values (1), (2);
QUERY PLAN
-------------------------------------------------------------------
Insert on public.pt (cost=0.00..0.03 rows=2 width=4)
Foreign Insert on public.fp1
Remote SQL: INSERT INTO public.t1(a) VALUES ($1)
Foreign Insert on public.fp2
Remote SQL: INSERT INTO public.t2(a) VALUES ($1)
-> Values Scan on "*VALUES*" (cost=0.00..0.03 rows=2 width=4)
Output: "*VALUES*".column1
(7 rows)
I think I should add more explanation about the patch, but I don't
have time today, so I'll write additional explanation in the next
email. Sorry about that.
Could you update your patch, it isn't applied on the actual state of
master. Namely conflict in src/backend/executor/execMain.c
Attached is an updated version.
* As mentioned in "Query planning", the patch builds an RTE for each
partition so that the FDW can make reference to that RTE in eg,
PlanForeignModify. set_plan_references also uses such RTEs to record
plan dependencies for plancache.c to invalidate cached plans. See an
example for that added to the regression tests in postgres_fdw.
* As mentioned in "explain.c", the EXPLAIN shows all partitions beneath
the ModifyTable node. One merit of that is we can show remote queries
for foreign partitions in the output as shown above. Another one I can
think of is when reporting execution stats for triggers. Here is an
example for that:
postgres=# explain analyze insert into list_parted values (1, 'hi
there'), (2, 'hi there');
QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Insert on list_parted (cost=0.00..0.03 rows=2 width=36) (actual
time=0.375..0.375 rows=0 loops=1)
Insert on part1
Insert on part2
-> Values Scan on "*VALUES*" (cost=0.00..0.03 rows=2 width=36)
(actual time=0.004..0.007 rows=2 loops=1)
Planning time: 0.089 ms
Trigger part1brtrig on part1: time=0.059 calls=1
Trigger part2brtrig on part2: time=0.021 calls=1
Execution time: 0.422 ms
(8 rows)
This would allow the user to understand easily that "part1" and "part2"
in the trigger lines are the partitions of list_parted. So, I think
it's useful to show partition info in the ModifyTable node even in the
case where a partitioned table only contains plain tables.
* The patch modifies make_modifytable and ExecInitModifyTable so that
the former can allow the FDW to construct private plan data for each
foreign partition and accumulate it all into a list, and that the latter
can perform BeginForeignModify for each partition using that plan data
stored in the list passed from make_modifytable. Other changes I made
to the executor are: (1) currently, we set the RT index for the root
partitioned table to ri_RangeTableIndex of partitions' ResultRelInfos,
but the proposed EXPLAIN requires that the partition's
ri_RangeTableIndex is set to the RT index for that partition's RTE, to
show that partition info in the output. So, I made that change.
Because of that, ExecPartitionCheck, ExecConstraints, and
ExecWithCheckOptions are adjusted accordingly. (2) I added a new member
to ResultRelInfo (ie, ri_PartitionIsValid), and modified
CheckValidResultRel so that it checks a given partition without throwing
an error and save the result in that flag so that ExecInsert determines
using that flag whether a partition chosen by ExecFindPartition is valid
for tuple-routing as proposed before.
* copy.c: I still don't think it's a good idea to implement COPY
tuple-routing for foreign partitions using PlanForeignModify. (I plan
to propose new FDW APIs for bulkload as discussed before, to implement
this feature.) So, I kept that as-is. Two things I changed there are:
(1) currently, ExecSetupPartitionTupleRouting verifies partitions using
CheckValidResultRel, but I don't think we need the CheckValidResultRel
check in the COPY case. So, I refactored that function and checked
partitions directly. (2) I think it'd be better to distinguish the
error message "cannot route inserted tuples to a foreign partition" in
the COPY case from the INSERT case, I changed it to "cannot route copied
tuples to a foreign partition".
* Fixed some bugs, revised comments, added a bit more regression tests,
and rebased the patch.
Comments are welcome!
My apologies for the very late reply.
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/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2459,2474 **** 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,
--- 2459,2477 ----
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,
***************
*** 2480,2485 **** CopyFrom(CopyState cstate)
--- 2483,2522 ----
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
***************
*** 2628,2638 **** 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
--- 2665,2680 ----
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
***************
*** 116,121 **** static void ExplainModifyTarget(ModifyTable *plan,
ExplainState *es);
--- 116,125 ----
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
ExplainState *es);
+ static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine
*fdwroutine,
+ 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,
***************
*** 828,833 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 832,848 ----
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;
***************
*** 2855,2911 **** 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 */
--- 2870,2896 ----
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 */
***************
*** 2962,2967 **** show_modifytable_info(ModifyTableState *mtstate, List
*ancestors,
--- 2947,3024 ----
}
/*
+ * 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
***************
*** 1102,1113 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType
operation)
--- 1102,1116 ----
Relation resultRel = resultRelInfo->ri_RelationDesc;
TriggerDesc *trigDesc = resultRel->trigdesc;
FdwRoutine *fdwroutine;
+ bool is_valid;
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
CheckCmdReplicaIdentity(resultRel, operation);
+ if (resultRelInfo->ri_PartitionRoot && operation ==
CMD_INSERT)
+ resultRelInfo->ri_PartitionIsValid = true;
break;
case RELKIND_SEQUENCE:
ereport(ERROR,
***************
*** 1174,1197 **** 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)
--- 1177,1204 ----
switch (operation)
{
case CMD_INSERT:
! is_valid = true;
if (fdwroutine->ExecForeignInsert ==
NULL)
! {
! if
(!resultRelInfo->ri_PartitionRoot)
! ereport(ERROR,
!
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!
errmsg("cannot insert into foreign table \"%s\"",
!
RelationGetRelationName(resultRel))));
! is_valid = false;
! }
if (fdwroutine->IsForeignRelUpdatable
!= NULL &&
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
! {
! if
(!resultRelInfo->ri_PartitionRoot)
! ereport(ERROR,
!
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!
errmsg("foreign table \"%s\" does not allow inserts",
!
RelationGetRelationName(resultRel))));
! is_valid = false;
! }
! if (resultRelInfo->ri_PartitionRoot)
!
resultRelInfo->ri_PartitionIsValid = is_valid;
break;
case CMD_UPDATE:
if (fdwroutine->ExecForeignUpdate ==
NULL)
***************
*** 1308,1314 **** void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! Relation partition_root,
int instrument_options)
{
List *partition_check = NIL;
--- 1315,1321 ----
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! ResultRelInfo *partition_root,
int instrument_options)
{
List *partition_check = NIL;
***************
*** 1364,1371 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
*/
partition_check = RelationGetPartitionQual(resultRelationDesc);
- resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
}
/*
--- 1371,1379 ----
*/
partition_check = RelationGetPartitionQual(resultRelationDesc);
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionCheck = partition_check;
+ resultRelInfo->ri_PartitionIsValid = false;
}
/*
***************
*** 1889,1903 **** 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,
--- 1897,1912 ----
{
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,
***************
*** 1908,1917 **** 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,
--- 1917,1931 ----
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,
***************
*** 1963,1968 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1977,1983 ----
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
***************
*** 1971,1982 **** 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,
--- 1986,1997 ----
* 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,
***************
*** 1987,1996 **** 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,
--- 2002,2016 ----
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,
***************
*** 2016,2030 **** 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,
--- 2036,2051 ----
{
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,
***************
*** 2035,2044 **** 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,
--- 2056,2070 ----
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,
***************
*** 2110,2115 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo
*resultRelInfo,
--- 2136,2142 ----
*/
if (!ExecQual(wcoExpr, econtext))
{
+ ResultRelInfo *rootRelInfo =
resultRelInfo->ri_PartitionRoot;
char *val_desc;
Bitmapset *modifiedCols;
Bitmapset *insertedCols;
***************
*** 2128,2140 **** 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,
--- 2155,2167 ----
*/
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,
***************
*** 2145,2154 **** 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,
--- 2172,2186 ----
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,
***************
*** 3242,3247 **** EvalPlanQualEnd(EPQState *epqstate)
--- 3274,3281 ----
* 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
***************
*** 3262,3288 **** EvalPlanQualEnd(EPQState *epqstate)
*/
void
ExecSetupPartitionTupleRouting(Relation rel,
- Index resultRTindex,
- EState *estate,
PartitionDispatch
**pd,
ResultRelInfo
***partitions,
TupleConversionMap
***tup_conv_maps,
TupleTableSlot
**partition_tuple_slot,
int *num_parted, int
*num_partitions)
{
- TupleDesc tupDesc = RelationGetDescr(rel);
- List *leaf_parts;
- ListCell *cell;
- int i;
- ResultRelInfo *leaf_part_rri;
-
/*
* Get the information about the partition tree after locking all the
* partitions.
*/
(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock,
NULL);
! *pd = RelationGetPartitionDispatchInfo(rel, num_parted, &leaf_parts);
! *num_partitions = list_length(leaf_parts);
*partitions = (ResultRelInfo **) palloc(*num_partitions *
sizeof(ResultRelInfo *));
*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
--- 3296,3315 ----
*/
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 *
***************
*** 3295,3350 **** 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++;
! }
}
/*
--- 3322,3378 ----
* 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
***************
*** 305,315 **** ExecInsert(ModifyTableState *mtstate,
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions[leaf_part_index];
! /* We do not yet have a way to insert into a foreign partition
*/
! if (resultRelInfo->ri_FdwRoutine)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples
to a foreign table")));
/* For ExecInsertIndexTuples() to work on the partition's
indexes */
estate->es_result_relation_info = resultRelInfo;
--- 305,321 ----
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions[leaf_part_index];
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* 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;
***************
*** 1945,1960 **** 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,
--- 1951,1967 ----
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,
***************
*** 1965,1970 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int
eflags)
--- 1972,2015 ----
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++;
+ }
}
/*
***************
*** 2390,2395 **** ExecEndModifyTable(ModifyTableState *node)
--- 2435,2445 ----
{
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"
***************
*** 6425,6430 **** make_modifytable(PlannerInfo *root,
--- 6426,6432 ----
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
Bitmapset *direct_modify_plans;
+ List *partition_rels;
ListCell *lc;
int i;
***************
*** 6548,6553 **** make_modifytable(PlannerInfo *root,
--- 6550,6682 ----
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; build an array to save
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);
+
+ /* Ignore 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;
+
+ /* 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.
+ */
+ tlist = preprocess_targetlist(root,
+
child_parse->targetList);
+ subplan->targetlist = tlist;
+
+ fdw_private =
fdwroutine->PlanForeignModify(root,
+
node,
+
child_rti,
+
0);
+ }
+
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list,
fdw_private);
+ }
+
+ root->parse = parent_parses[nominalRelation];
+ node->nominalRelation = nominalRelation;
+ node->resultRelations = list_make1_int(nominalRelation);
+ node->withCheckOptionLists = saved_withCheckOptionLists;
+ node->returningLists = saved_returningLists;
+ subplan->targetlist = saved_tlist;
+ }
+ node->partition_rels = partition_rels;
+ node->fdwPartitionPrivLists = fdw_private_list;
+
return node;
}
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 870,879 **** 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
--- 870,881 ----
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
***************
*** 854,859 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 854,863 ----
{
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
***************
*** 1972,1979 **** 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);
--- 1972,1980 ----
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);
***************
*** 2323,2329 **** 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
--- 2324,2330 ----
}
/*
! * 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
***************
*** 2335,2342 **** 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)
--- 2336,2341 ----
*** 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/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);
***************
*** 207,219 **** extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
extern void ExecSetupPartitionTupleRouting(Relation rel,
- Index resultRTindex,
- EState *estate,
PartitionDispatch
**pd,
ResultRelInfo
***partitions,
TupleConversionMap
***tup_conv_maps,
TupleTableSlot
**partition_tuple_slot,
int *num_parted, int
*num_partitions);
extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
PartitionDispatch *pd,
TupleTableSlot *slot,
--- 207,224 ----
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
extern void ExecSetupPartitionTupleRouting(Relation rel,
PartitionDispatch
**pd,
+ List **leaf_parts,
ResultRelInfo
***partitions,
TupleConversionMap
***tup_conv_maps,
TupleTableSlot
**partition_tuple_slot,
int *num_parted, int
*num_partitions);
+ extern void ExecInitPartition(EState *estate,
+ ResultRelInfo *rootRelInfo,
+ Oid partOid,
+ Index partRTindex,
+ ResultRelInfo *partRelInfo,
+ TupleConversionMap **partTupConvMap);
extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
PartitionDispatch *pd,
TupleTableSlot *slot,
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 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;
/* ----------------
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers