Hi,
Here is a patch for $subject. This is based on Amit's original patch
(patch '0007-Tuple-routing-for-partitioned-tables-15.patch' in [1]).
Main changes are:
* I like Amit's idea that for each partition that is a foreign table,
the FDW performs query planning in the same way as in non-partition
cases, but the changes to make_modifytable() in the original patch that
creates a working-copy of Query to pass to PlanForeignModify() seem
unacceptable. So, I modified the changes so that we create
more-valid-looking copies of Query and ModifyTable with the foreign
partition as target, as I said before. In relation to this, I also
allowed expand_inherited_rtentry() to build an RTE and AppendRelInfo for
each partition of a partitioned table that is an INSERT target, as
mentioned by Amit in [1], by modifying transformInsertStmt() so that we
set the inh flag for the target table's RTE to true when the target
table is a partitioned table. The partition RTEs are not only
referenced by FDWs, but used in selecting relation aliases for EXPLAIN
(see below) as well as extracting plan dependencies in setref.c so that
we force rebuilding of the plan on any change to partitions. (We do
replanning on FDW table-option changes to foreign partitions, currently.
See regression tests in the attached patch.)
* The original patch added tuple routing to foreign partitions in COPY
FROM, but I'm not sure the changes to BeginCopy() in the patch are the
right way to go. For example, the patch passes a dummy empty Query to
PlanForeignModify() to make FDWs perform query planning, but I think
FDWs would be surprised by the Query. Currently, we don't support COPY
to foreign tables, so ISTM that it's better to revisit this issue when
adding support for COPY to foreign tables. So, I dropped the COPY part.
* I modified explain.c so that EXPLAIN for an INSERT into a partitioned
table displays partitions just below the ModifyTable node, and shows
remote queries for foreign partitions in the same way as EXPLAIN for
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)
Comments are welcome! Anyway, I'll add this to the next commitfest.
Best regards,
Etsuro Fujita
[1]
https://www.postgresql.org/message-id/aa874eaf-cd3b-0f75-c647-6c0ea823781e%40lab.ntt.co.jp
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 6931,6936 **** NOTICE: drop cascades to foreign table bar2
--- 6931,7074 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing to foreign-table partitions
+ -- ===================================================================
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback
options (table_name 'locfoo');
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ insert into pt values (1, 1), (2, 1);
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ remp | 2 | 1
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ (1 row)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ (1 row)
+
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ QUERY PLAN
+
--------------------------------------------------------------------------------
+ Insert on public.pt
+ Output: pt.a, pt.b
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2) RETURNING a,
b
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (7 rows)
+
+ insert into pt values (1, 2), (2, 2) returning *;
+ a | b
+ ---+---
+ 1 | 2
+ 2 | 2
+ (2 rows)
+
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ remp | 2 | 1
+ remp | 2 | 2
+ (4 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ remp | 2 | 2
+ (2 rows)
+
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locbar(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ execute q1;
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ locp | 1 | 3
+ remp | 2 | 1
+ remp | 2 | 2
+ remp | 2 | 3
+ (6 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ locp | 1 | 3
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ remp | 2 | 2
+ remp | 2 | 3
+ (3 rows)
+
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1615,1620 **** drop table loct1;
--- 1615,1660 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing to foreign-table partitions
+ -- ===================================================================
+
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback
options (table_name 'locfoo');
+
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ insert into pt values (1, 1), (2, 1);
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ insert into pt values (1, 2), (2, 2) returning *;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ execute q1;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 116,121 **** static void ExplainModifyTarget(ModifyTable *plan,
ExplainState *es);
--- 116,125 ----
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
ExplainState *es);
+ static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine
*fdwroutine,
+ bool main_target, int subplan_index,
+ const char *operation, bool labeltarget,
ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
***************
*** 821,826 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 825,841 ----
if (((ModifyTable *) plan)->exclRelRTI)
*rels_used = bms_add_member(*rels_used,
((ModifyTable *) plan)->exclRelRTI);
+ if (((ModifyTable *) plan)->partition_rels)
+ {
+ ListCell *lc;
+
+ foreach(lc, ((ModifyTable *)
plan)->partition_rels)
+ {
+ Index rti = lfirst_int(lc);
+
+ *rels_used = bms_add_member(*rels_used,
rti);
+ }
+ }
break;
default:
break;
***************
*** 2792,2853 **** show_modifytable_info(ModifyTableState *mtstate, List
*ancestors,
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
{
ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! if (labeltargets)
! {
! /* Open a group for this target */
! ExplainOpenGroup("Target Table", NULL, true, es);
!
! /*
! * In text mode, decorate each target with operation
type, so that
! * ExplainTargetRel's output of " on foo" will read
nicely.
! */
! if (es->format == EXPLAIN_FORMAT_TEXT)
! {
! appendStringInfoSpaces(es->str, es->indent * 2);
! appendStringInfoString(es->str,
!
fdwroutine ? foperation : operation);
! }
!
! /* Identify target */
! ExplainTargetRel((Plan *) node,
!
resultRelInfo->ri_RangeTableIndex,
! es);
!
! if (es->format == EXPLAIN_FORMAT_TEXT)
! {
! appendStringInfoChar(es->str, '\n');
! es->indent++;
! }
! }
!
! /* Give FDW a chance if needed */
! if (!resultRelInfo->ri_usesFdwDirectModify &&
! fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
! {
! List *fdw_private = (List *)
list_nth(node->fdwPrivLists, j);
!
! fdwroutine->ExplainForeignModify(mtstate,
!
resultRelInfo,
!
fdw_private,
!
j,
!
es);
! }
! if (labeltargets)
! {
! /* Undo the indentation we added in text format */
! if (es->format == EXPLAIN_FORMAT_TEXT)
! es->indent--;
! /* Close the group */
! ExplainCloseGroup("Target Table", NULL, true, es);
! }
}
/* Gather names of ON CONFLICT arbiter indexes */
--- 2807,2835 ----
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
+ /* Print main target(s) */
for (j = 0; j < mtstate->mt_nplans; j++)
{
ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! show_actual_target(mtstate, node, resultRelInfo, fdwroutine,
true, j,
! fdwroutine ? foperation :
operation, labeltargets,
! es);
! }
! /* If this is an INSERT into a partitioned table, print partitions */
! for (j = 0; j < mtstate->mt_num_partitions; j++)
! {
! /*
! * We arrange mt_partitions in the plan's partition_rels list
order,
! * which is the same order as for UPDATE/DELETE cases.
! */
! ResultRelInfo *resultRelInfo = mtstate->mt_partitions +
mtstate->mt_partition_indexes[j];
! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! show_actual_target(mtstate, node, resultRelInfo, fdwroutine,
false, j,
! fdwroutine ? foperation :
operation, true, es);
}
/* Gather names of ON CONFLICT arbiter indexes */
***************
*** 2904,2909 **** show_modifytable_info(ModifyTableState *mtstate, List
*ancestors,
--- 2886,2957 ----
}
/*
+ * Show an actual target relation
+ */
+ static void
+ show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine
*fdwroutine,
+ bool main_target, int subplan_index,
+ const char *operation, bool labeltarget,
ExplainState *es)
+ {
+ if (labeltarget)
+ {
+ /* Open a group for this target */
+ ExplainOpenGroup("Target Table", NULL, true, es);
+
+ /*
+ * In text mode, decorate each target with operation type, so
that
+ * ExplainTargetRel's output of " on foo" will read nicely.
+ */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, operation);
+ }
+
+ /* Identify target */
+ ExplainTargetRel((Plan *) node,
+
resultRelInfo->ri_RangeTableIndex,
+ es);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoChar(es->str, '\n');
+ es->indent++;
+ }
+ }
+
+ /* Give FDW a chance if needed */
+ if (fdwroutine != NULL &&
+ fdwroutine->ExplainForeignModify != NULL &&
+ !resultRelInfo->ri_usesFdwDirectModify)
+ {
+ List *fdw_private;
+
+ if (main_target)
+ fdw_private = (List *) list_nth(node->fdwPrivLists,
subplan_index);
+ else
+ fdw_private = (List *)
list_nth(node->fdwPartitionPrivLists, subplan_index);
+
+ fdwroutine->ExplainForeignModify(mtstate,
+
resultRelInfo,
+
fdw_private,
+
main_target ? subplan_index : 0,
+
es);
+ }
+
+ if (labeltarget)
+ {
+ /* Undo the indentation we added in text format */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+
+ /* Close the group */
+ ExplainCloseGroup("Target Table", NULL, true, es);
+ }
+ }
+
+ /*
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
* BitmapAnd, or BitmapOr node.
*
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 92,97 **** static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
--- 92,99 ----
Bitmapset *modifiedCols,
AclMode requiredPerms);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
+ static ResultRelInfo *ExecFindResultRelInfo(EState *estate, Oid relid,
+
bool missing_ok);
static char *ExecBuildSlotValueDescription(Oid reloid,
TupleTableSlot *slot,
TupleDesc tupdesc,
***************
*** 1360,1365 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1362,1392 ----
}
/*
+ * ExecFindResultRelInfo -- find the ResultRelInfo struct for given relation
OID
+ *
+ * If no such struct, either return NULL or throw error depending on
missing_ok
+ */
+ static ResultRelInfo *
+ ExecFindResultRelInfo(EState *estate, Oid relid, bool missing_ok)
+ {
+ ResultRelInfo *rInfo;
+ int nr;
+
+ rInfo = estate->es_result_relations;
+ nr = estate->es_num_result_relations;
+ while (nr > 0)
+ {
+ if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ return rInfo;
+ rInfo++;
+ nr--;
+ }
+ if (!missing_ok)
+ elog(ERROR, "failed to find ResultRelInfo for relation %u",
relid);
+ return NULL;
+ }
+
+ /*
* ExecGetTriggerResultRel
*
* Get a ResultRelInfo for a trigger target relation. Most of the time,
***************
*** 1379,1399 **** ResultRelInfo *
ExecGetTriggerResultRel(EState *estate, Oid relid)
{
ResultRelInfo *rInfo;
- int nr;
ListCell *l;
Relation rel;
MemoryContext oldcontext;
/* First, search through the query result relations */
! rInfo = estate->es_result_relations;
! nr = estate->es_num_result_relations;
! while (nr > 0)
! {
! if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
! return rInfo;
! rInfo++;
! nr--;
! }
/* Nope, but maybe we already made an extra ResultRelInfo for it */
foreach(l, estate->es_trig_target_relations)
{
--- 1406,1419 ----
ExecGetTriggerResultRel(EState *estate, Oid relid)
{
ResultRelInfo *rInfo;
ListCell *l;
Relation rel;
MemoryContext oldcontext;
/* First, search through the query result relations */
! rInfo = ExecFindResultRelInfo(estate, relid, true);
! if (rInfo)
! return rInfo;
/* Nope, but maybe we already made an extra ResultRelInfo for it */
foreach(l, estate->es_trig_target_relations)
{
***************
*** 1828,1833 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
--- 1848,1854 ----
EState *estate)
{
Relation rel = resultRelInfo->ri_RelationDesc;
+ Oid relid = RelationGetRelid(rel);
TupleDesc tupdesc = RelationGetDescr(rel);
Bitmapset *modifiedCols;
Bitmapset *insertedCols;
***************
*** 1870,1877 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
--- 1891,1900 ----
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
+ ResultRelInfo *rInfo;
rel = resultRelInfo->ri_PartitionRoot;
+ relid = RelationGetRelid(rel);
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 1881,1892 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
tuple = do_convert_tuple(tuple, map);
ExecStoreTuple(tuple, slot, InvalidBuffer,
false);
}
}
-
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
! val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
tupdesc,
modifiedCols,
--- 1904,1921 ----
tuple = do_convert_tuple(tuple, map);
ExecStoreTuple(tuple, slot, InvalidBuffer,
false);
}
+ /* Find the root table's ResultRelInfo */
+ rInfo = ExecFindResultRelInfo(estate, relid, false);
+ insertedCols = GetInsertedColumns(rInfo, estate);
+ updatedCols = GetUpdatedColumns(rInfo, estate);
+ }
+ else
+ {
+ insertedCols = GetInsertedColumns(resultRelInfo,
estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
}
modifiedCols = bms_union(insertedCols, updatedCols);
! val_desc = ExecBuildSlotValueDescription(relid,
slot,
tupdesc,
modifiedCols,
***************
*** 1914,1919 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1943,1949 ----
TupleTableSlot *slot, EState *estate)
{
Relation rel = resultRelInfo->ri_RelationDesc;
+ Oid relid = RelationGetRelid(rel);
TupleDesc tupdesc = RelationGetDescr(rel);
TupleConstr *constr = tupdesc->constr;
Bitmapset *modifiedCols;
***************
*** 1947,1954 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1977,1986 ----
{
HeapTuple tuple =
ExecFetchSlotTuple(slot);
TupleConversionMap *map;
+ ResultRelInfo *rInfo;
rel = resultRelInfo->ri_PartitionRoot;
+ relid = RelationGetRelid(rel);
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map =
convert_tuples_by_name(orig_tupdesc, tupdesc,
***************
*** 1958,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo,
tuple = do_convert_tuple(tuple,
map);
ExecStoreTuple(tuple, slot,
InvalidBuffer, false);
}
}
-
- insertedCols =
GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo,
estate);
modifiedCols = bms_union(insertedCols,
updatedCols);
! val_desc =
ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
tupdesc,
modifiedCols,
--- 1990,2007 ----
tuple = do_convert_tuple(tuple,
map);
ExecStoreTuple(tuple, slot,
InvalidBuffer, false);
}
+ /* Find the root table's ResultRelInfo
*/
+ rInfo = ExecFindResultRelInfo(estate,
relid, false);
+ insertedCols =
GetInsertedColumns(rInfo, estate);
+ updatedCols = GetUpdatedColumns(rInfo,
estate);
+ }
+ else
+ {
+ insertedCols =
GetInsertedColumns(resultRelInfo, estate);
+ updatedCols =
GetUpdatedColumns(resultRelInfo, estate);
}
modifiedCols = bms_union(insertedCols,
updatedCols);
! val_desc = ExecBuildSlotValueDescription(relid,
slot,
tupdesc,
modifiedCols,
***************
*** 1987,1992 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 2025,2031 ----
{
char *val_desc;
Relation orig_rel = rel;
+ Oid relid = RelationGetRelid(rel);
/* See the comment above. */
if (resultRelInfo->ri_PartitionRoot)
***************
*** 1994,2001 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 2033,2042 ----
HeapTuple tuple =
ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc =
RelationGetDescr(rel);
TupleConversionMap *map;
+ ResultRelInfo *rInfo;
rel = resultRelInfo->ri_PartitionRoot;
+ relid = RelationGetRelid(rel);
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc,
tupdesc,
***************
*** 2005,2016 **** ExecConstraints(ResultRelInfo *resultRelInfo,
tuple = do_convert_tuple(tuple, map);
ExecStoreTuple(tuple, slot,
InvalidBuffer, false);
}
}
-
- insertedCols = GetInsertedColumns(resultRelInfo,
estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
! val_desc =
ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
tupdesc,
modifiedCols,
--- 2046,2063 ----
tuple = do_convert_tuple(tuple, map);
ExecStoreTuple(tuple, slot,
InvalidBuffer, false);
}
+ /* Find the root table's ResultRelInfo */
+ rInfo = ExecFindResultRelInfo(estate, relid,
false);
+ insertedCols = GetInsertedColumns(rInfo,
estate);
+ updatedCols = GetUpdatedColumns(rInfo, estate);
+ }
+ else
+ {
+ insertedCols =
GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo,
estate);
}
modifiedCols = bms_union(insertedCols, updatedCols);
! val_desc = ExecBuildSlotValueDescription(relid,
slot,
tupdesc,
modifiedCols,
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 304,315 **** ExecInsert(ModifyTableState *mtstate,
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions + leaf_part_index;
- /* We do not yet have a way to insert into a foreign partition
*/
- if (resultRelInfo->ri_FdwRoutine)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot route inserted tuples
to a foreign table")));
-
/* For ExecInsertIndexTuples() to work on the partition's
indexes */
estate->es_result_relation_info = resultRelInfo;
--- 304,309 ----
***************
*** 1925,1930 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int
eflags)
--- 1919,1970 ----
mtstate->mt_num_partitions = num_partitions;
mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
mtstate->mt_partition_tuple_slot = partition_tuple_slot;
+
+ mtstate->mt_partition_indexes = (int *)
+ palloc0(num_partitions * sizeof(int));
+ i = 0;
+ foreach(l, node->partition_rels)
+ {
+ Index rti = lfirst_int(l);
+ Oid relid = getrelid(rti,
estate->es_range_table);
+ bool found;
+ int j;
+
+ /* First, find the ResultRelInfo for the partition */
+ found = false;
+ for (j = 0; j < mtstate->mt_num_partitions; j++)
+ {
+ resultRelInfo = partitions + j;
+
+ if
(RelationGetRelid(resultRelInfo->ri_RelationDesc) == relid)
+ {
+ Assert(mtstate->mt_partition_indexes[i]
== 0);
+ mtstate->mt_partition_indexes[i] = j;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ elog(ERROR, "failed to find ResultRelInfo for
rangetable index %u", rti);
+
+ /* Fix the rangetable index of the ResultRelInfo */
+ resultRelInfo->ri_RangeTableIndex = rti;
+
+ /* Let FDWs init themselves for foreign-table result
rels */
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+
resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *)
list_nth(node->fdwPartitionPrivLists, i);
+
+
resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+
resultRelInfo,
+
fdw_private,
+
0,
+
eflags);
+ }
+
+ i++;
+ }
}
/* Build state for collecting transition tuples */
***************
*** 2345,2350 **** ExecEndModifyTable(ModifyTableState *node)
--- 2385,2395 ----
{
ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+
resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+
resultRelInfo);
+
ExecCloseIndices(resultRelInfo);
heap_close(resultRelInfo->ri_RelationDesc, NoLock);
}
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 204,209 **** _copyModifyTable(const ModifyTable *from)
--- 204,210 ----
COPY_SCALAR_FIELD(canSetTag);
COPY_SCALAR_FIELD(nominalRelation);
COPY_NODE_FIELD(partitioned_rels);
+ COPY_NODE_FIELD(partition_rels);
COPY_NODE_FIELD(resultRelations);
COPY_SCALAR_FIELD(resultRelIndex);
COPY_SCALAR_FIELD(rootResultRelIndex);
***************
*** 220,225 **** _copyModifyTable(const ModifyTable *from)
--- 221,227 ----
COPY_NODE_FIELD(onConflictWhere);
COPY_SCALAR_FIELD(exclRelRTI);
COPY_NODE_FIELD(exclRelTlist);
+ COPY_NODE_FIELD(fdwPartitionPrivLists);
return newnode;
}
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 349,354 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 349,355 ----
WRITE_BOOL_FIELD(canSetTag);
WRITE_UINT_FIELD(nominalRelation);
WRITE_NODE_FIELD(partitioned_rels);
+ WRITE_NODE_FIELD(partition_rels);
WRITE_NODE_FIELD(resultRelations);
WRITE_INT_FIELD(resultRelIndex);
WRITE_INT_FIELD(rootResultRelIndex);
***************
*** 365,370 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 366,372 ----
WRITE_NODE_FIELD(onConflictWhere);
WRITE_UINT_FIELD(exclRelRTI);
WRITE_NODE_FIELD(exclRelTlist);
+ WRITE_NODE_FIELD(fdwPartitionPrivLists);
}
static void
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1547,1552 **** _readModifyTable(void)
--- 1547,1553 ----
READ_BOOL_FIELD(canSetTag);
READ_UINT_FIELD(nominalRelation);
READ_NODE_FIELD(partitioned_rels);
+ READ_NODE_FIELD(partition_rels);
READ_NODE_FIELD(resultRelations);
READ_INT_FIELD(resultRelIndex);
READ_INT_FIELD(rootResultRelIndex);
***************
*** 1563,1568 **** _readModifyTable(void)
--- 1564,1570 ----
READ_NODE_FIELD(onConflictWhere);
READ_UINT_FIELD(exclRelRTI);
READ_NODE_FIELD(exclRelTlist);
+ READ_NODE_FIELD(fdwPartitionPrivLists);
READ_DONE();
}
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 35,40 ****
--- 35,41 ----
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/predtest.h"
+ #include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
***************
*** 6405,6410 **** make_modifytable(PlannerInfo *root,
--- 6406,6412 ----
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
Bitmapset *direct_modify_plans;
+ List *partition_rels;
ListCell *lc;
int i;
***************
*** 6525,6530 **** make_modifytable(PlannerInfo *root,
--- 6527,6600 ----
node->fdwPrivLists = fdw_private_list;
node->fdwDirectModifyPlans = direct_modify_plans;
+ /*
+ * Also, if this is an INSERT into a partitioned table, build a list of
+ * RT indexes of partitions, and for each partition that is a foreign
table,
+ * allow the FDW to construct private plan data and accumulate it all
into
+ * another list.
+ */
+ partition_rels = NIL;
+ fdw_private_list = NIL;
+ if (operation == CMD_INSERT)
+ {
+ Index rti = linitial_int(resultRelations);
+
+ if (planner_rt_fetch(rti, root)->relkind ==
RELKIND_PARTITIONED_TABLE)
+ {
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = (AppendRelInfo *)
lfirst(lc);
+ Index part_rti = appinfo->child_relid;
+ RangeTblEntry *part_rte;
+ FdwRoutine *fdwroutine = NULL;
+ List *fdw_private = NIL;
+
+ /* append_rel_list contains all append rels;
ignore others */
+ if (appinfo->parent_relid != rti)
+ continue;
+
+ part_rte = planner_rt_fetch(part_rti, root);
+ Assert(part_rte->rtekind == RTE_RELATION);
+
+ if (part_rte->relkind == RELKIND_FOREIGN_TABLE)
+ fdwroutine =
GetFdwRoutineByRelId(part_rte->relid);
+
+ if (fdwroutine != NULL &&
+ fdwroutine->PlanForeignModify != NULL)
+ {
+ PlannerInfo *part_root;
+ ModifyTable *part_node;
+
+ /* Create a working copy of the
PlannerInfo */
+ part_root = makeNode(PlannerInfo);
+ memcpy(part_root, root,
sizeof(PlannerInfo));
+ /* Generate modified query with this
partition as target */
+ part_root->parse = (Query *)
+ adjust_appendrel_attrs(root,
+
(Node *) root->parse,
+
appinfo);
+
+ /* Create a working copy of the
ModifyTable */
+ part_node = copyObject(node);
+ part_node->nominalRelation = part_rti;
+ part_node->resultRelations =
list_make1_int(part_rti);
+ part_node->returningLists =
+
list_make1(part_root->parse->returningList);
+
+ fdw_private =
fdwroutine->PlanForeignModify(part_root,
+
part_node,
+
part_rti,
+
0);
+ }
+
+ partition_rels = lappend_int(partition_rels,
part_rti);
+ fdw_private_list = lappend(fdw_private_list,
fdw_private);
+ }
+ }
+ }
+ node->partition_rels = partition_rels;
+ node->fdwPartitionPrivLists = fdw_private_list;
+
return node;
}
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 828,834 **** subquery_planner(PlannerGlobal *glob, Query *parse,
* Do the main planning. If we have an inherited target relation, that
* needs special processing, else go straight to grouping_planner.
*/
! if (parse->resultRelation &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
--- 828,835 ----
* Do the main planning. If we have an inherited target relation, that
* needs special processing, else go straight to grouping_planner.
*/
! if ((parse->commandType == CMD_UPDATE ||
! parse->commandType == CMD_DELETE) &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 850,855 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 850,859 ----
{
lfirst_int(l) += rtoffset;
}
+ foreach(l, splan->partition_rels)
+ {
+ lfirst_int(l) += rtoffset;
+ }
foreach(l, splan->resultRelations)
{
lfirst_int(l) += rtoffset;
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 542,547 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 542,554 ----
qry->resultRelation = setTargetTable(pstate, stmt->relation,
false, false, targetPerms);
+ /*
+ * If the target table is a partitioned table, reset the inh flag to
true.
+ */
+ rte = pstate->p_target_rangetblentry;
+ if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+ rte->inh = true;
+
/* Validate stmt->cols list, or build default list if no list given */
icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
Assert(list_length(icolumns) == list_length(attrnos));
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 2859,2871 **** rewriteTargetView(Query *parsetree, Relation view)
new_rt_index = list_length(parsetree->rtable);
/*
- * INSERTs never inherit. For UPDATE/DELETE, we use the view query's
- * inheritance flag for the base relation.
- */
- if (parsetree->commandType == CMD_INSERT)
- new_rte->inh = false;
-
- /*
* Adjust the view's targetlist Vars to reference the new target RTE, ie
* make their varnos be new_rt_index instead of base_rt_index. There
can
* be no Vars for other rels in the tlist, so this is sufficient to pull
--- 2859,2864 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 963,968 **** typedef struct ModifyTableState
--- 963,970 ----
TupleConversionMap **mt_partition_tupconv_maps;
/* Per partition tuple conversion map */
TupleTableSlot *mt_partition_tuple_slot;
+ int *mt_partition_indexes; /* indexes into
mt_partitions for use
+
* in explain.c */
struct TransitionCaptureState *mt_transition_capture;
/*
controls transition table population */
TupleConversionMap **mt_transition_tupconv_maps;
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 219,224 **** typedef struct ModifyTable
--- 219,226 ----
Index nominalRelation; /* Parent RT index for use of
EXPLAIN */
/* RT indexes of non-leaf tables in a partition tree */
List *partitioned_rels;
+ List *partition_rels; /* RT indexes of leaf tables in a
partition
+ * tree */
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in
plan's list */
int rootResultRelIndex; /* index of the partitioned
table root */
***************
*** 235,240 **** typedef struct ModifyTable
--- 237,244 ----
Node *onConflictWhere; /* WHERE for ON CONFLICT UPDATE */
Index exclRelRTI; /* RTI of the EXCLUDED pseudo
relation */
List *exclRelTlist; /* tlist of the EXCLUDED pseudo
relation */
+ List *fdwPartitionPrivLists; /* per-partition FDW private
data
+
* lists */
} ModifyTable;
/* ----------------
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers