Add support for tuple routing to foreign partitions
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]/messages/by-id/aa874eaf-cd3b-0f75-c647-6c0ea823781e@lab.ntt.co.jp).
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]/messages/by-id/aa874eaf-cd3b-0f75-c647-6c0ea823781e@lab.ntt.co.jp, 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]: /messages/by-id/aa874eaf-cd3b-0f75-c647-6c0ea823781e@lab.ntt.co.jp
/messages/by-id/aa874eaf-cd3b-0f75-c647-6c0ea823781e@lab.ntt.co.jp
Attachments:
tuple-routing-to-foreign-partitions-v1.patchtext/plain; charset=UTF-8; name=tuple-routing-to-foreign-partitions-v1.patchDownload
*** 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;
/* ----------------
Fujita-san,
On 2017/06/29 20:20, Etsuro Fujita wrote:
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]).
Thanks a lot for taking this up.
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.
This sounds good.
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.
About this part, Robert sounded a little unsure back then [1]/messages/by-id/CA+TgmoYvp6DD1jpwb9sNe08E7jSFEFky32TJU+sJ8OStHYW3nA@mail.gmail.com; his words:
"Doing more inheritance expansion early is probably not a good idea
because it likely sucks for performance, and that's especially unfortunate
if it's only needed for foreign tables."
But...
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.)
...this part means we cannot really avoid locking at least the foreign
partitions during the planning stage, which we currently don't, as we
don't look at partitions at all before ExecSetupPartitionTupleRouting() is
called by ExecInitModifyTable().
Since we are locking *all* the partitions anyway, it may be better to
shift the cost of locking to the planner by doing the inheritance
expansion even in the insert case (for partitioned tables) and avoid
calling the lock manager again in the executor when setting up
tuple-routing data structures (that is, in ExecSetupPartitionTupleRouting).
An idea that Robert recently mentioned on the nearby "UPDATE partition
key" thread [2]/messages/by-id/CA+TgmoajC0J50=2FqnZLvB10roY+68HgFWhso=V_StkC6PWujQ@mail.gmail.com seems relevant in this regard. By that idea,
expand_inherited_rtentry(), instead of reading in the partition OIDs in
the old catalog order, will instead call
RelationBuildPartitionDispatchInfo(), which locks the partitions and
returns their OIDs in the canonical order. If we store the foreign
partition RT indexes in that order in ModifyTable.partition_rels
introduced by this patch, then the following code added by the patch could
be made more efficient (as also explained in aforementioned Robert's email):
+ 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);
* 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 agree.
Thanks,
Amit
[1]: /messages/by-id/CA+TgmoYvp6DD1jpwb9sNe08E7jSFEFky32TJU+sJ8OStHYW3nA@mail.gmail.com
/messages/by-id/CA+TgmoYvp6DD1jpwb9sNe08E7jSFEFky32TJU+sJ8OStHYW3nA@mail.gmail.com
[2]: /messages/by-id/CA+TgmoajC0J50=2FqnZLvB10roY+68HgFWhso=V_StkC6PWujQ@mail.gmail.com
/messages/by-id/CA+TgmoajC0J50=2FqnZLvB10roY+68HgFWhso=V_StkC6PWujQ@mail.gmail.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2017/07/05 9:13, Amit Langote wrote:
On 2017/06/29 20:20, Etsuro Fujita wrote:
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.About this part, Robert sounded a little unsure back then [1]; his words:
"Doing more inheritance expansion early is probably not a good idea
because it likely sucks for performance, and that's especially unfortunate
if it's only needed for foreign tables."But...
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.)...this part means we cannot really avoid locking at least the foreign
partitions during the planning stage, which we currently don't, as we
don't look at partitions at all before ExecSetupPartitionTupleRouting() is
called by ExecInitModifyTable().
Another case where we need inheritance expansion during the planning
stage would probably be INSERT .. ON CONFLICT into a partitioned table,
I guess. We don't support that yet, but if implementing that, I guess
we would probably need to look at each partition and do something like
infer_arbiter_indexes() for each partition during the planner stage.
Not sure.
Since we are locking *all* the partitions anyway, it may be better to
shift the cost of locking to the planner by doing the inheritance
expansion even in the insert case (for partitioned tables) and avoid
calling the lock manager again in the executor when setting up
tuple-routing data structures (that is, in ExecSetupPartitionTupleRouting).
We need the execution-time lock, anyway. See the comments from Robert
in [3]/messages/by-id/CA+TgmoYiwviCDRi3Zk+QuXj1r7uMu9T_kDNq+17PCWgzrbzw8A@mail.gmail.com.
An idea that Robert recently mentioned on the nearby "UPDATE partition
key" thread [2] seems relevant in this regard. By that idea,
expand_inherited_rtentry(), instead of reading in the partition OIDs in
the old catalog order, will instead call
RelationBuildPartitionDispatchInfo(), which locks the partitions and
returns their OIDs in the canonical order. If we store the foreign
partition RT indexes in that order in ModifyTable.partition_rels
introduced by this patch, then the following code added by the patch could
be made more efficient (as also explained in aforementioned Robert's email):+ 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);
Agreed. I really want to improve this based on that idea.
Thank you for the comments!
Best regards,
Etsuro Fujita
[3]: /messages/by-id/CA+TgmoYiwviCDRi3Zk+QuXj1r7uMu9T_kDNq+17PCWgzrbzw8A@mail.gmail.com
/messages/by-id/CA+TgmoYiwviCDRi3Zk+QuXj1r7uMu9T_kDNq+17PCWgzrbzw8A@mail.gmail.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jun 29, 2017 at 6:20 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
So, I dropped the COPY part.
Ouch. I think we should try to figure out how the COPY part will be
handled before we commit to a design.
I have to admit that I'm a little bit fuzzy about why foreign insert
routing requires all of these changes. I think this patch would
benefit from being accompanied by several paragraphs of explanation
outlining the rationale for each part of the patch.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2017/07/11 6:56, Robert Haas wrote:
On Thu, Jun 29, 2017 at 6:20 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:So, I dropped the COPY part.
Ouch. I think we should try to figure out how the COPY part will be
handled before we commit to a design.
I spent some time on this. To handle that, I'd like to propose doing
something similar to \copy (frontend copy): submit a COPY query "COPY
... FROM STDIN" to the remote server and route data from a file to the
remote server. For that, I'd like to add new FDW APIs called during
CopyFrom that allow us to copy to foreign tables:
* BeginForeignCopyIn: this would be called after creating a
ResultRelInfo for the target table (or each leaf partition of the target
partitioned table) if it's a foreign table, and perform any
initialization needed before the remote copy can start. In the
postgres_fdw case, I think this function would be a good place to send
"COPY ... FROM STDIN" to the remote server.
* ExecForeignCopyInOneRow: this would be called instead of heap_insert
if the target is a foreign table, and route the tuple read from the file
by NextCopyFrom to the remote server. In the postgres_fdw case, I think
this function would convert the tuple to text format for portability,
and then send the data to the remote server using PQputCopyData.
* EndForeignCopyIn: this would be called at the bottom of CopyFrom, and
release resources such as connections to the remote server. In the
postgres_fdw case, this function would do PQputCopyEnd to terminate data
transfer.
I think that would be much more efficient than INSERTing tuples into the
remote server one by one. What do you think about that?
I have to admit that I'm a little bit fuzzy about why foreign insert
routing requires all of these changes. I think this patch would
benefit from being accompanied by several paragraphs of explanation
outlining the rationale for each part of the patch.
Will do.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 17, 2017 at 1:57 PM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
On 2017/07/11 6:56, Robert Haas wrote:
On Thu, Jun 29, 2017 at 6:20 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:So, I dropped the COPY part.
Ouch. I think we should try to figure out how the COPY part will be
handled before we commit to a design.I spent some time on this. To handle that, I'd like to propose doing
something similar to \copy (frontend copy): submit a COPY query "COPY ...
FROM STDIN" to the remote server and route data from a file to the remote
server. For that, I'd like to add new FDW APIs called during CopyFrom that
allow us to copy to foreign tables:
The description seems to support only COPY to a foreign table from a
file, but probably we need the support other way round as well. This
looks like a feature (support copy to and from a foreign table) to be
handled by itself.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2017/08/17 20:37, Ashutosh Bapat wrote:
On Thu, Aug 17, 2017 at 1:57 PM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:I spent some time on this. To handle that, I'd like to propose doing
something similar to \copy (frontend copy): submit a COPY query "COPY ...
FROM STDIN" to the remote server and route data from a file to the remote
server. For that, I'd like to add new FDW APIs called during CopyFrom that
allow us to copy to foreign tables:The description seems to support only COPY to a foreign table from a
file, but probably we need the support other way round as well. This
looks like a feature (support copy to and from a foreign table) to be
handled by itself.
Agreed. I'll consider how to handle copy-from-a-foreign-table as well.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 17, 2017 at 05:27:05PM +0900, Etsuro Fujita wrote:
On 2017/07/11 6:56, Robert Haas wrote:
On Thu, Jun 29, 2017 at 6:20 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:So, I dropped the COPY part.
Ouch. I think we should try to figure out how the COPY part will be
handled before we commit to a design.I spent some time on this. To handle that, I'd like to propose doing
something similar to \copy (frontend copy): submit a COPY query "COPY ...
FROM STDIN" to the remote server and route data from a file to the remote
server. For that, I'd like to add new FDW APIs called during CopyFrom that
allow us to copy to foreign tables:* BeginForeignCopyIn: this would be called after creating a ResultRelInfo
for the target table (or each leaf partition of the target partitioned
table) if it's a foreign table, and perform any initialization needed before
the remote copy can start. In the postgres_fdw case, I think this function
would be a good place to send "COPY ... FROM STDIN" to the remote server.* ExecForeignCopyInOneRow: this would be called instead of heap_insert if
the target is a foreign table, and route the tuple read from the file by
NextCopyFrom to the remote server. In the postgres_fdw case, I think this
function would convert the tuple to text format for portability, and then
send the data to the remote server using PQputCopyData.* EndForeignCopyIn: this would be called at the bottom of CopyFrom, and
release resources such as connections to the remote server. In the
postgres_fdw case, this function would do PQputCopyEnd to terminate data
transfer.
These primitives look good. I know it seems unlikely at first blush,
but do we know of bulk load APIs for non-PostgreSQL data stores that
this would be unable to serve?
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2017/08/17 23:48, David Fetter wrote:
On Thu, Aug 17, 2017 at 05:27:05PM +0900, Etsuro Fujita wrote:
On 2017/07/11 6:56, Robert Haas wrote:
On Thu, Jun 29, 2017 at 6:20 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:So, I dropped the COPY part.
Ouch. I think we should try to figure out how the COPY part will be
handled before we commit to a design.I spent some time on this. To handle that, I'd like to propose doing
something similar to \copy (frontend copy): submit a COPY query "COPY ...
FROM STDIN" to the remote server and route data from a file to the remote
server. For that, I'd like to add new FDW APIs called during CopyFrom that
allow us to copy to foreign tables:* BeginForeignCopyIn: this would be called after creating a ResultRelInfo
for the target table (or each leaf partition of the target partitioned
table) if it's a foreign table, and perform any initialization needed before
the remote copy can start. In the postgres_fdw case, I think this function
would be a good place to send "COPY ... FROM STDIN" to the remote server.* ExecForeignCopyInOneRow: this would be called instead of heap_insert if
the target is a foreign table, and route the tuple read from the file by
NextCopyFrom to the remote server. In the postgres_fdw case, I think this
function would convert the tuple to text format for portability, and then
send the data to the remote server using PQputCopyData.* EndForeignCopyIn: this would be called at the bottom of CopyFrom, and
release resources such as connections to the remote server. In the
postgres_fdw case, this function would do PQputCopyEnd to terminate data
transfer.These primitives look good. I know it seems unlikely at first blush,
but do we know of bulk load APIs for non-PostgreSQL data stores that
this would be unable to serve?
Maybe I'm missing something, but I think these would allow the FDW to do
the remote copy the way it would like; in ExecForeignCopyInOneRow, for
example, the FDW could buffer tuples in a buffer memory and transmit the
buffered data to the remote server if the data size exceeds a threshold.
The naming is not so good? Suggestions are welcome.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Aug 18, 2017 at 05:10:29PM +0900, Etsuro Fujita wrote:
On 2017/08/17 23:48, David Fetter wrote:
On Thu, Aug 17, 2017 at 05:27:05PM +0900, Etsuro Fujita wrote:
On 2017/07/11 6:56, Robert Haas wrote:
On Thu, Jun 29, 2017 at 6:20 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:So, I dropped the COPY part.
Ouch. I think we should try to figure out how the COPY part will be
handled before we commit to a design.I spent some time on this. To handle that, I'd like to propose doing
something similar to \copy (frontend copy): submit a COPY query "COPY ...
FROM STDIN" to the remote server and route data from a file to the remote
server. For that, I'd like to add new FDW APIs called during CopyFrom that
allow us to copy to foreign tables:* BeginForeignCopyIn: this would be called after creating a ResultRelInfo
for the target table (or each leaf partition of the target partitioned
table) if it's a foreign table, and perform any initialization needed before
the remote copy can start. In the postgres_fdw case, I think this function
would be a good place to send "COPY ... FROM STDIN" to the remote server.* ExecForeignCopyInOneRow: this would be called instead of heap_insert if
the target is a foreign table, and route the tuple read from the file by
NextCopyFrom to the remote server. In the postgres_fdw case, I think this
function would convert the tuple to text format for portability, and then
send the data to the remote server using PQputCopyData.* EndForeignCopyIn: this would be called at the bottom of CopyFrom, and
release resources such as connections to the remote server. In the
postgres_fdw case, this function would do PQputCopyEnd to terminate data
transfer.These primitives look good. I know it seems unlikely at first
blush, but do we know of bulk load APIs for non-PostgreSQL data
stores that this would be unable to serve?Maybe I'm missing something, but I think these would allow the FDW
to do the remote copy the way it would like; in
ExecForeignCopyInOneRow, for example, the FDW could buffer tuples in
a buffer memory and transmit the buffered data to the remote server
if the data size exceeds a threshold. The naming is not so good?
Suggestions are welcome.
The naming seems reasonable.
I was trying to figure out whether this fits well enough with the bulk
load APIs for databases other than PostgreSQL. I'm guessing it's
"well enough" based on checking MySQL, Oracle, and MS SQL Server.
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 17, 2017 at 7:58 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
The description seems to support only COPY to a foreign table from a
file, but probably we need the support other way round as well. This
looks like a feature (support copy to and from a foreign table) to be
handled by itself.Agreed. I'll consider how to handle copy-from-a-foreign-table as well.
That's a completely different feature which has nothing to do with
tuple routing.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 17, 2017 at 4:27 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
I think that would be much more efficient than INSERTing tuples into the
remote server one by one. What do you think about that?
I agree, but I wonder if we ought to make it work first using the
existing APIs and then add these new APIs as an optimization.
postgres_fdw isn't the only FDW in the world, and it's better if
getting a working implementation doesn't require too many interfaces.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2017/08/18 22:41, David Fetter wrote:
On Fri, Aug 18, 2017 at 05:10:29PM +0900, Etsuro Fujita wrote:
On 2017/08/17 23:48, David Fetter wrote:
On Thu, Aug 17, 2017 at 05:27:05PM +0900, Etsuro Fujita wrote:
On 2017/07/11 6:56, Robert Haas wrote:
On Thu, Jun 29, 2017 at 6:20 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:So, I dropped the COPY part.
Ouch. I think we should try to figure out how the COPY part will be
handled before we commit to a design.I spent some time on this. To handle that, I'd like to propose doing
something similar to \copy (frontend copy): submit a COPY query "COPY ...
FROM STDIN" to the remote server and route data from a file to the remote
server. For that, I'd like to add new FDW APIs called during CopyFrom that
allow us to copy to foreign tables:* BeginForeignCopyIn: this would be called after creating a ResultRelInfo
for the target table (or each leaf partition of the target partitioned
table) if it's a foreign table, and perform any initialization needed before
the remote copy can start. In the postgres_fdw case, I think this function
would be a good place to send "COPY ... FROM STDIN" to the remote server.* ExecForeignCopyInOneRow: this would be called instead of heap_insert if
the target is a foreign table, and route the tuple read from the file by
NextCopyFrom to the remote server. In the postgres_fdw case, I think this
function would convert the tuple to text format for portability, and then
send the data to the remote server using PQputCopyData.* EndForeignCopyIn: this would be called at the bottom of CopyFrom, and
release resources such as connections to the remote server. In the
postgres_fdw case, this function would do PQputCopyEnd to terminate data
transfer.These primitives look good. I know it seems unlikely at first
blush, but do we know of bulk load APIs for non-PostgreSQL data
stores that this would be unable to serve?Maybe I'm missing something, but I think these would allow the FDW
to do the remote copy the way it would like; in
ExecForeignCopyInOneRow, for example, the FDW could buffer tuples in
a buffer memory and transmit the buffered data to the remote server
if the data size exceeds a threshold. The naming is not so good?
Suggestions are welcome.The naming seems reasonable.
I was trying to figure out whether this fits well enough with the bulk
load APIs for databases other than PostgreSQL. I'm guessing it's
"well enough" based on checking MySQL, Oracle, and MS SQL Server.
Good to know.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2017/08/19 2:12, Robert Haas wrote:
On Thu, Aug 17, 2017 at 4:27 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:I think that would be much more efficient than INSERTing tuples into the
remote server one by one. What do you think about that?I agree, but I wonder if we ought to make it work first using the
existing APIs and then add these new APIs as an optimization.
I'm not sure that's a good idea because that once we support INSERT
tuple-routing for foreign partitions, we would have a workaround: INSERT
INTO partitioned_table SELECT * from data_table where data_table is a
foreign table defined for an input file using file_fdw.
postgres_fdw isn't the only FDW in the world, and it's better if
getting a working implementation doesn't require too many interfaces.
Agreed. My vote would be to leave the COPY part as-is until we have
these new APIs.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Aug 20, 2017 at 11:25 PM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
I agree, but I wonder if we ought to make it work first using the
existing APIs and then add these new APIs as an optimization.I'm not sure that's a good idea because that once we support INSERT
tuple-routing for foreign partitions, we would have a workaround: INSERT
INTO partitioned_table SELECT * from data_table where data_table is a
foreign table defined for an input file using file_fdw.
That's true, but I don't see how it refutes the point I was trying to raise.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2017/08/26 1:43, Robert Haas wrote:
On Sun, Aug 20, 2017 at 11:25 PM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:I agree, but I wonder if we ought to make it work first using the
existing APIs and then add these new APIs as an optimization.I'm not sure that's a good idea because that once we support INSERT
tuple-routing for foreign partitions, we would have a workaround: INSERT
INTO partitioned_table SELECT * from data_table where data_table is a
foreign table defined for an input file using file_fdw.That's true, but I don't see how it refutes the point I was trying to raise.
My concern is: the existing APIs can really work well for any FDW to do
COPY tuple-routing? I know the original version of the patch showed the
existing APIs would work well for postgres_fdw but nothing beyond. For
example: the original version made a dummy Query/Plan only containing a
leaf partition as its result relation, and passed it to
PlanForeignModify, IIRC. That would work well for postgres_fdw because
it doesn't look at the Query/Plan except the result relation, but might
not for other FDWs that look at more stuff of the given Query/Plan and
do something based on that information. Some FDW might check to see
whether the Plan is to do INSERT .. VALUES with a single VALUES sublist
or INSERT .. VALUES with multiple VALUES sublists, for optimizing remote
INSERT, for example.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2017/08/17 17:27, Etsuro Fujita wrote:
On 2017/07/11 6:56, Robert Haas wrote:
I have to admit that I'm a little bit fuzzy about why foreign insert
routing requires all of these changes. I think this patch would
benefit from being accompanied by several paragraphs of explanation
outlining the rationale for each part of the patch.Will do.
Here is an updated version of the patch.
* 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.
Best regards,
Etsuro Fujita
Attachments:
tuple-routing-to-foreign-partitions-v2.patchtext/plain; charset=UTF-8; name=tuple-routing-to-foreign-partitions-v2.patchDownload
*** 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;
***************
*** 342,348 **** SELECT tableoid::regclass, * FROM p2;
(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
--- 342,348 ----
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to foreign table "p1"
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,7172 ----
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
***************
*** 1662,1667 **** drop table loct1;
--- 1662,1707 ----
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/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2417,2422 **** CopyFrom(CopyState cstate)
--- 2417,2423 ----
cstate->rel,
1, /* dummy rangetable index */
NULL,
+ 0, /* dummy rangetable index */
0);
ExecOpenIndices(resultRelInfo, false);
***************
*** 2446,2461 **** 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,
--- 2447,2465 ----
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,
***************
*** 2467,2472 **** CopyFrom(CopyState cstate)
--- 2471,2508 ----
cstate->partition_tupconv_maps = partition_tupconv_maps;
cstate->partition_tuple_slot = partition_tuple_slot;
+ partRelInfo = partitions;
+ i = 0;
+ foreach(l, partition_oids)
+ {
+ Oid partOid = lfirst_oid(l);
+ Relation partrel;
+
+ /* Prepare map and ResultRelInfo for the partition */
+ ExecInitPartition(estate,
+ partOid,
+ 0, /* dummy rangetable index */
+ resultRelInfo,
+ partRelInfo,
+ &partition_tupconv_maps[i]);
+
+ /* 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
+ {
+ /* 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
***************
*** 2618,2628 **** 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
--- 2654,2669 ----
saved_resultRelInfo = resultRelInfo;
resultRelInfo = cstate->partitions + leaf_part_index;
! /* We do not yet have a way to copy into a foreign partition */
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* Should be foreign */
! Assert(resultRelInfo->ri_FdwRoutine);
!
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,
+ 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,
***************
*** 834,839 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 838,854 ----
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;
***************
*** 2856,2916 **** 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);
}
}
--- 2871,2919 ----
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++)
! {
! ResultRelInfo *resultRelInfo = mtstate->mt_partitions + j;
! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! if (resultRelInfo->ri_PartitionIsValid)
{
! Oid partOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
! int k;
! ListCell *cell;
! bool found;
! /* First, find the subplan index for the partition */
! found = false;
! k = 0;
! foreach(cell, node->partition_rels)
! {
! Index partRTindex = lfirst_int(cell);
! if (getrelid(partRTindex, es->rtable) == partOid)
! {
! found = true;
! break;
! }
! k++;
! }
! if (!found)
! elog(ERROR, "failed to find subplan index for relation %u", partOid);
! show_actual_target(mtstate, node, resultRelInfo, fdwroutine, false, k,
! fdwroutine ? foperation : operation, true, es);
}
}
***************
*** 2968,2973 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
--- 2971,3042 ----
}
/*
+ * 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/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 1403,1408 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1403,1409 ----
rel,
0, /* dummy rangetable index */
NULL,
+ 0, /* dummy rangetable index */
0);
resultRelInfo++;
}
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 114,120 **** static void ExecPartitionCheck(ResultRelInfo *resultRelInfo,
* to be changed, however.
*/
#define GetInsertedColumns(relinfo, estate) \
! (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols)
#define GetUpdatedColumns(relinfo, estate) \
(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
--- 114,123 ----
* to be changed, however.
*/
#define GetInsertedColumns(relinfo, estate) \
! (rt_fetch((relinfo)->ri_PartitionRoot ? \
! (relinfo)->ri_PartitionRootRTindex : \
! (relinfo)->ri_RangeTableIndex, \
! (estate)->es_range_table)->insertedCols)
#define GetUpdatedColumns(relinfo, estate) \
(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
***************
*** 854,859 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 857,863 ----
resultRelation,
resultRelationIndex,
NULL,
+ 0, /* dummy rangetable index */
estate->es_instrument);
resultRelInfo++;
}
***************
*** 893,898 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 897,903 ----
resultRelDesc,
lfirst_int(l),
NULL,
+ 0, /* dummy rangetable index */
estate->es_instrument);
resultRelInfo++;
}
***************
*** 1102,1113 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
--- 1107,1121 ----
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,1196 **** 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)
--- 1182,1209 ----
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,1313 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1321,1327 ----
Relation resultRelationDesc,
Index resultRelationIndex,
Relation partition_root,
+ Index partition_root_rtindex,
int instrument_options)
{
List *partition_check = NIL;
***************
*** 1365,1370 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1379,1386 ----
resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionRootRTindex = partition_root_rtindex;
+ resultRelInfo->ri_PartitionIsValid = false;
}
/*
***************
*** 1447,1452 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
--- 1463,1469 ----
rel,
0, /* dummy rangetable index */
NULL,
+ 0, /* dummy rangetable index */
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
***************
*** 3245,3250 **** EvalPlanQualEnd(EPQState *epqstate)
--- 3262,3269 ----
* 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
***************
*** 3265,3291 **** 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 *
--- 3284,3303 ----
*/
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 *
***************
*** 3298,3352 **** ExecSetupPartitionTupleRouting(Relation rel,
* processing.
*/
*partition_tuple_slot = MakeTupleTableSlot();
! leaf_part_rri = *partitions;
! 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);
! leaf_part_rri++;
! i++;
! }
}
/*
--- 3310,3366 ----
* processing.
*/
*partition_tuple_slot = MakeTupleTableSlot();
+ }
! /*
! * ExecInitPartition -- Prepare tuple conversion map and ResultRelInfo for
! * the partition with OID 'partOid'
! */
! void
! ExecInitPartition(EState *estate,
! Oid partOid,
! Index partRTindex,
! ResultRelInfo *rootRelInfo,
! 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 a tuple 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"));
! /* Save a ResultRelInfo data for the partition. */
! InitResultRelInfo(partRelInfo,
! partrel,
! partRTindex,
! rootrel,
! rootRelInfo->ri_RangeTableIndex,
! 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);
! estate->es_leaf_result_relations =
! lappend(estate->es_leaf_result_relations, partRelInfo);
}
/*
*** 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)
! {
! /* 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;
***************
*** 1912,1927 **** 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,
--- 1918,1935 ----
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;
+ Index rootRTindex;
+ ResultRelInfo *partRelInfo;
ExecSetupPartitionTupleRouting(rel,
&partition_dispatch_info,
+ &partition_oids,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
***************
*** 1932,1937 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1940,2002 ----
mtstate->mt_num_partitions = num_partitions;
mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
mtstate->mt_partition_tuple_slot = partition_tuple_slot;
+
+ rootRTindex = mtstate->resultRelInfo->ri_RangeTableIndex;
+ partRelInfo = partitions;
+ i = 0;
+ foreach(l, partition_oids)
+ {
+ Oid partOid = lfirst_oid(l);
+ Index partRTindex;
+ int j;
+ ListCell *cell;
+ bool found;
+
+ /* First, find the RT index for the partition */
+ found = false;
+ j = 0;
+ foreach(cell, node->partition_rels)
+ {
+ partRTindex = lfirst_int(cell);
+
+ if (getrelid(partRTindex, estate->es_range_table) == partOid)
+ {
+ found = true;
+ break;
+ }
+ j++;
+ }
+ if (!found)
+ elog(ERROR, "failed to find range table index for relation %u", partOid);
+
+ /* Prepare map and ResultRelInfo for the partition */
+ ExecInitPartition(estate,
+ partOid,
+ partRTindex,
+ mtstate->resultRelInfo,
+ partRelInfo,
+ &partition_tupconv_maps[i]);
+
+ /* Verify the partition is a valid target for INSERT */
+ CheckValidResultRel(partRelInfo, CMD_INSERT);
+
+ /* If so, let the FDW 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, j);
+
+ partRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ partRelInfo,
+ fdw_private,
+ 0,
+ eflags);
+ }
+
+ partRelInfo++;
+ i++;
+ }
}
/* Build state for collecting transition tuples */
***************
*** 2361,2366 **** ExecEndModifyTable(ModifyTableState *node)
--- 2426,2436 ----
{
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
***************
*** 367,372 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 367,373 ----
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);
***************
*** 383,388 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 384,390 ----
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
***************
*** 1562,1567 **** _readModifyTable(void)
--- 1562,1568 ----
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);
***************
*** 1578,1583 **** _readModifyTable(void)
--- 1579,1585 ----
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"
***************
*** 6412,6417 **** make_modifytable(PlannerInfo *root,
--- 6413,6419 ----
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
Bitmapset *direct_modify_plans;
+ List *partition_rels;
ListCell *lc;
int i;
***************
*** 6535,6540 **** make_modifytable(PlannerInfo *root,
--- 6537,6637 ----
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)
+ {
+ Query *saved_query = root->parse;
+ Index saved_nominalRelation = node->nominalRelation;
+ List *saved_resultRelations = node->resultRelations;
+ List *saved_withCheckOptionLists = node->withCheckOptionLists;
+ List *saved_returningLists = node->returningLists;
+ Plan *subplan = (Plan *) linitial(node->plans);
+ List *saved_tlist = subplan->targetlist;
+
+ 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)
+ {
+ List *tlist;
+
+ /*
+ * Replace the Query node with the modified one that has
+ * this partition as target.
+ */
+ root->parse = (Query *)
+ adjust_appendrel_attrs(root,
+ (Node *) root->parse,
+ 1, &appinfo);
+
+ /*
+ * Likewise for the ModifyTable node.
+ */
+ node->nominalRelation = part_rti;
+ node->resultRelations = list_make1_int(part_rti);
+ node->withCheckOptionLists =
+ list_make1(root->parse->withCheckOptions);
+ node->returningLists =
+ list_make1(root->parse->returningList);
+
+ /*
+ * Adjust the subplan's tlist because the column list of
+ * this partition might have a different column order
+ * and/or a different set of dropped columns than the
+ * partitioned table root.
+ */
+ tlist = preprocess_targetlist(root,
+ root->parse->targetList);
+ subplan->targetlist = tlist;
+
+ fdw_private = fdwroutine->PlanForeignModify(root,
+ node,
+ part_rti,
+ 0);
+ }
+
+ partition_rels = lappend_int(partition_rels, part_rti);
+ fdw_private_list = lappend(fdw_private_list, fdw_private);
+ }
+
+ root->parse = saved_query;
+ node->nominalRelation = saved_nominalRelation;
+ node->resultRelations = saved_resultRelations;
+ 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
***************
*** 840,848 **** 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
--- 840,850 ----
/*
* Do the main planning. If we have an inherited target relation, that
! * needs special processing except for INSERT cases, 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/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 22,30 ****
* The fact that rewriteTargetListIU sorts non-resjunk tlist entries by column
* position, which expand_targetlist depends on, violates the above comment
* because the sorting is only valid for the parent relation. In inherited
! * UPDATE cases, adjust_inherited_tlist runs in between to take care of fixing
! * the tlists for child tables to keep expand_targetlist happy. We do it like
! * that because it's faster in typical non-inherited cases.
*
*
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
--- 22,30 ----
* The fact that rewriteTargetListIU sorts non-resjunk tlist entries by column
* position, which expand_targetlist depends on, violates the above comment
* because the sorting is only valid for the parent relation. In inherited
! * INSERT/UPDATE cases, adjust_inherited_tlist runs in between to take care of
! * fixing the tlists for child tables to keep expand_targetlist happy. We do
! * it like that because it's faster in typical non-inherited cases.
*
*
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1929,1936 **** 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);
--- 1929,1937 ----
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);
***************
*** 2227,2233 **** adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos)
}
/*
! * 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
--- 2228,2234 ----
}
/*
! * 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
***************
*** 2239,2246 **** adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos)
* 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)
--- 2240,2245 ----
*** 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/replication/logical/worker.c
--- b/src/backend/replication/logical/worker.c
***************
*** 198,204 **** create_estate_for_relation(LogicalRepRelMapEntry *rel)
estate->es_range_table = list_make1(rte);
resultRelInfo = makeNode(ResultRelInfo);
! InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
--- 198,204 ----
estate->es_range_table = list_make1(rte);
resultRelInfo = makeNode(ResultRelInfo);
! InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0, 0);
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
*** 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
***************
*** 182,187 **** extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 182,188 ----
Relation resultRelationDesc,
Index resultRelationIndex,
Relation partition_root,
+ Index partition_root_rtindex,
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,
--- 208,225 ----
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,
+ Oid partOid,
+ Index partRTindex,
+ ResultRelInfo *rootRelInfo,
+ 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
***************
*** 412,417 **** typedef struct ResultRelInfo
--- 412,423 ----
/* relation descriptor for root partitioned table */
Relation ri_PartitionRoot;
+
+ /* range table index for root partitioned table */
+ Index ri_PartitionRootRTindex;
+
+ /* 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;
/* ----------------
Hi Fujita-san!
On 11.09.2017 16:01, Etsuro Fujita wrote:
Here is an updated version of the patch.
* 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
--
Regards,
Maksim Milyutin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
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
Attachments:
tuple-routing-to-foreign-partitions-v3.patchtext/plain; charset=UTF-8; name=tuple-routing-to-foreign-partitions-v3.patchDownload
*** 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;
/* ----------------
On 2017/10/26 16:40, Etsuro Fujita wrote:
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.
One thing I forgot to mention is: that would be also required to call
BeginForeignModify, ExecForeignInsert, and EndForeignModify with the
partition's ResultRelInfo.
I updated docs in doc/src/sgml/ddl.sgml the same way as [1]/messages/by-id/b19a8e2b-e000-f592-3e0b-3e90ba0fa816@lab.ntt.co.jp. (I used
only the ddl.sgml change proposed by [1]/messages/by-id/b19a8e2b-e000-f592-3e0b-3e90ba0fa816@lab.ntt.co.jp, not all the changes.) I did
some cleanup as well. Please find attached an updated version of the patch.
Best regards,
Etsuro Fujita
[1]: /messages/by-id/b19a8e2b-e000-f592-3e0b-3e90ba0fa816@lab.ntt.co.jp
/messages/by-id/b19a8e2b-e000-f592-3e0b-3e90ba0fa816@lab.ntt.co.jp
Attachments:
tuple-routing-to-foreign-partitions-v4.patchtext/plain; charset=UTF-8; name=tuple-routing-to-foreign-partitions-v4.patchDownload
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 176,182 **** COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
--- 176,188 ----
SELECT tableoid::regclass, * FROM pt;
SELECT tableoid::regclass, * FROM p1;
SELECT tableoid::regclass, * FROM p2;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (1, 'xyzzy');
+ \t off
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (2, 'xyzzy');
+ \t off
INSERT INTO pt VALUES (2, 'xyzzy');
SELECT tableoid::regclass, * FROM pt;
SELECT tableoid::regclass, * FROM p1;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 315,321 **** SELECT tableoid::regclass, * FROM p2;
(0 rows)
COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
--- 315,321 ----
(0 rows)
COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
! ERROR: cannot route copied tuples to a foreign table
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
***************
*** 341,348 **** SELECT tableoid::regclass, * FROM p2;
p2 | 2 | qux
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
--- 341,366 ----
p2 | 2 | qux
(2 rows)
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (1, 'xyzzy');
+ Insert on public.pt
+ Foreign Insert on public.p1
+ Insert on public.p2
+ -> Result
+ Output: 1, 'xyzzy'::text
+
+ \t off
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to foreign table "p1"
! \t on
! EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (2, 'xyzzy');
! Insert on public.pt
! Foreign Insert on public.p1
! Insert on public.p2
! -> Result
! Output: 2, 'xyzzy'::text
!
! \t off
INSERT INTO pt VALUES (2, 'xyzzy');
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7029,7034 **** NOTICE: drop cascades to foreign table bar2
--- 7029,7236 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo');
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ insert into pt values (1, 1), (2, 1);
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ remp | 2 | 1
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ (1 row)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ (1 row)
+
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ QUERY PLAN
+ --------------------------------------------------------------------------------
+ Insert on public.pt
+ Output: pt.a, pt.b
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2) RETURNING a, b
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (7 rows)
+
+ insert into pt values (1, 2), (2, 2) returning *;
+ a | b
+ ---+---
+ 1 | 2
+ 2 | 2
+ (2 rows)
+
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ remp | 2 | 1
+ remp | 2 | 2
+ (4 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ remp | 2 | 2
+ (2 rows)
+
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locbar(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ execute q1;
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ locp | 1 | 3
+ remp | 2 | 1
+ remp | 2 | 2
+ remp | 2 | 3
+ (6 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ locp | 1 | 3
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ remp | 2 | 2
+ remp | 2 | 3
+ (3 rows)
+
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+ -- Check INSERT into a multi-level partitioned table
+ create table mlpt (a int, b int, c varchar) partition by range (a);
+ create table mlptp1 partition of mlpt for values from (100) to (200) partition by range(b);
+ create table mlptp2 partition of mlpt for values from (200) to (300);
+ create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 100 and b < 200), c varchar);
+ create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 200 and b < 300), c varchar);
+ create foreign table mlptp1p1 partition of mlptp1 for values from (100) to (200) server loopback options (table_name 'locfoo');
+ create foreign table mlptp1p2 partition of mlptp1 for values from (200) to (300) server loopback options (table_name 'locbar');
+ explain (verbose, costs off)
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ QUERY PLAN
+ ------------------------------------------------------------------------------------------
+ Insert on public.mlpt
+ Output: mlpt.a, mlpt.b, mlpt.c
+ Foreign Insert on public.mlptp1p1
+ Remote SQL: INSERT INTO public.locfoo(a, b, c) VALUES ($1, $2, $3) RETURNING a, b, c
+ Foreign Insert on public.mlptp1p2
+ Remote SQL: INSERT INTO public.locbar(a, b, c) VALUES ($1, $2, $3) RETURNING a, b, c
+ Insert on public.mlptp2
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2, "*VALUES*".column3
+ (9 rows)
+
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ a | b | c
+ -----+-----+---
+ 101 | 101 | x
+ 101 | 201 | y
+ (2 rows)
+
+ select tableoid::regclass, * FROM mlpt;
+ tableoid | a | b | c
+ ----------+-----+-----+---
+ mlptp1p1 | 101 | 101 | x
+ mlptp1p2 | 101 | 201 | y
+ (2 rows)
+
+ select tableoid::regclass, * FROM mlptp1;
+ tableoid | a | b | c
+ ----------+-----+-----+---
+ mlptp1p1 | 101 | 101 | x
+ mlptp1p2 | 101 | 201 | y
+ (2 rows)
+
+ select tableoid::regclass, * FROM mlptp2;
+ tableoid | a | b | c
+ ----------+---+---+---
+ (0 rows)
+
+ select tableoid::regclass, * FROM mlptp1p1;
+ tableoid | a | b | c
+ ----------+-----+-----+---
+ mlptp1p1 | 101 | 101 | x
+ (1 row)
+
+ select tableoid::regclass, * FROM mlptp1p2;
+ tableoid | a | b | c
+ ----------+-----+-----+---
+ mlptp1p2 | 101 | 201 | y
+ (1 row)
+
+ drop table mlpt;
+ drop table locfoo;
+ drop table locbar;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1662,1667 **** drop table loct1;
--- 1662,1730 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo');
+
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ insert into pt values (1, 1), (2, 1);
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ insert into pt values (1, 2), (2, 2) returning *;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ execute q1;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+
+ -- Check INSERT into a multi-level partitioned table
+ create table mlpt (a int, b int, c varchar) partition by range (a);
+ create table mlptp1 partition of mlpt for values from (100) to (200) partition by range(b);
+ create table mlptp2 partition of mlpt for values from (200) to (300);
+ create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 100 and b < 200), c varchar);
+ create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 200 and b < 300), c varchar);
+ create foreign table mlptp1p1 partition of mlptp1 for values from (100) to (200) server loopback options (table_name 'locfoo');
+ create foreign table mlptp1p2 partition of mlptp1 for values from (200) to (300) server loopback options (table_name 'locbar');
+
+ explain (verbose, costs off)
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+
+ select tableoid::regclass, * FROM mlpt;
+ select tableoid::regclass, * FROM mlptp1;
+ select tableoid::regclass, * FROM mlptp2;
+ select tableoid::regclass, * FROM mlptp1p1;
+ select tableoid::regclass, * FROM mlptp1p2;
+
+ drop table mlpt;
+ drop table locfoo;
+ drop table locbar;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2986,2996 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable">),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<sect3 id="ddl-partitioning-declarative-example">
--- 2986,2994 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<sect3 id="ddl-partitioning-declarative-example">
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 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,6681 ----
node->fdwPrivLists = fdw_private_list;
node->fdwDirectModifyPlans = direct_modify_plans;
+ /*
+ * Also, if this is an INSERT into a partitioned table, build a list of
+ * RT indexes of partitions, and for each foreign partition, allow the FDW
+ * to construct private plan data and accumulate it all into another list.
+ *
+ * Note: ExecSetupPartitionTupleRouting() will expand partitions in the
+ * same order as these lists.
+ */
+ partition_rels = NIL;
+ fdw_private_list = NIL;
+ if (operation == CMD_INSERT &&
+ planner_rt_fetch(nominalRelation, root)->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *saved_withCheckOptionLists = node->withCheckOptionLists;
+ List *saved_returningLists = node->returningLists;
+ Plan *subplan = (Plan *) linitial(node->plans);
+ List *saved_tlist = subplan->targetlist;
+ Query **parent_parses;
+ Query *parent_parse;
+ Bitmapset *parent_relids;
+
+ /*
+ * Similarly to inheritance_planner(), we generate a modified query
+ * with each child as target by applying adjust_appendrel_attrs() to
+ * the query for its immediate parent, so build an array to store in
+ * the query for each parent.
+ */
+ parent_parses = (Query **)
+ palloc0((list_length(root->parse->rtable) + 1) * sizeof(Query *));
+
+ parent_parses[nominalRelation] = root->parse;
+ parent_relids = bms_make_singleton(nominalRelation);
+
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ Index parent_rti = appinfo->parent_relid;
+ Index child_rti = appinfo->child_relid;
+ RangeTblEntry *child_rte;
+ Query *child_parse;
+ FdwRoutine *fdwroutine = NULL;
+ List *fdw_private = NIL;
+
+ /* append_rel_list contains all append rels; ignore others */
+ if (!bms_is_member(parent_rti, parent_relids))
+ continue;
+
+ child_rte = planner_rt_fetch(child_rti, root);
+ Assert(child_rte->rtekind == RTE_RELATION);
+
+ /* No work if the child is a plain table */
+ if (child_rte->relkind == RELKIND_RELATION)
+ {
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list, NIL);
+ continue;
+ }
+
+ /*
+ * expand_inherited_rtentry() always processes a parent before any
+ * of that parent's children, so the query for its parent should
+ * already be available.
+ */
+ parent_parse = parent_parses[parent_rti];
+ Assert(parent_parse);
+
+ /* Generate the query with the child as target */
+ child_parse = (Query *)
+ adjust_appendrel_attrs(root,
+ (Node *) parent_parse,
+ 1, &appinfo);
+
+ /* Store the query if the child is a partitioned table */
+ if (child_rte->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ parent_parses[child_rti] = child_parse;
+ parent_relids = bms_add_member(parent_relids, child_rti);
+ continue;
+ }
+
+ /* The child should be a foreign table */
+ Assert(child_rte->relkind == RELKIND_FOREIGN_TABLE);
+
+ fdwroutine = GetFdwRoutineByRelId(child_rte->relid);
+ Assert(fdwroutine != NULL);
+
+ if (fdwroutine->PlanForeignModify != NULL)
+ {
+ List *tlist;
+
+ /* Adjust the plan node to refer to the child as target. */
+ node->nominalRelation = child_rti;
+ node->resultRelations = list_make1_int(child_rti);
+ node->withCheckOptionLists =
+ list_make1(child_parse->withCheckOptions);
+ node->returningLists =
+ list_make1(child_parse->returningList);
+
+ /*
+ * The column list of the child might have a different column
+ * order and/or a different set of dropped columns than that
+ * of its parent, so adjust the subplan's tlist as well.
+ */
+ tlist = preprocess_targetlist(root,
+ child_parse->targetList);
+ subplan->targetlist = tlist;
+
+ fdw_private = fdwroutine->PlanForeignModify(root,
+ node,
+ child_rti,
+ 0);
+ }
+
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list, fdw_private);
+ }
+
+ 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;
/* ----------------
(2017/10/27 20:00), Etsuro Fujita wrote:
Please find attached an updated version of the patch.
Amit rebased this patch and sent me the rebased version off list.
Thanks for that, Amit!
One thing I noticed I overlooked is about this change I added to
make_modifytable to make a valid-looking plan node to pass to
PlanForeignModify to plan remote insert to each foreign partition:
+ /*
+ * The column list of the child might have a different
column
+ * order and/or a different set of dropped columns than that
+ * of its parent, so adjust the subplan's tlist as well.
+ */
+ tlist = preprocess_targetlist(root,
+ child_parse->targetList);
This would be needed because the FDW might reference the tlist. Since
preprocess_targetlist references root->parse, it's needed to replace
that with the child query before calling that function, but I forgot to
do that. So I fixed that. Attached is an updated version of the patch.
Best regards,
Etsuro Fujita
Attachments:
tuple-routing-to-foreign-partitions-v5.patchtext/plain; charset=Shift_JIS; name=tuple-routing-to-foreign-partitions-v5.patchDownload
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 176,182 **** COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
--- 176,188 ----
SELECT tableoid::regclass, * FROM pt;
SELECT tableoid::regclass, * FROM p1;
SELECT tableoid::regclass, * FROM p2;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (1, 'xyzzy');
+ \t off
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (2, 'xyzzy');
+ \t off
INSERT INTO pt VALUES (2, 'xyzzy');
SELECT tableoid::regclass, * FROM pt;
SELECT tableoid::regclass, * FROM p1;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 315,321 **** SELECT tableoid::regclass, * FROM p2;
(0 rows)
COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
--- 315,321 ----
(0 rows)
COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
! ERROR: cannot route copied tuples to a foreign table
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
***************
*** 341,348 **** SELECT tableoid::regclass, * FROM p2;
p2 | 2 | qux
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
--- 341,366 ----
p2 | 2 | qux
(2 rows)
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (1, 'xyzzy');
+ Insert on public.pt
+ Foreign Insert on public.p1
+ Insert on public.p2
+ -> Result
+ Output: 1, 'xyzzy'::text
+
+ \t off
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to foreign table "p1"
! \t on
! EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (2, 'xyzzy');
! Insert on public.pt
! Foreign Insert on public.p1
! Insert on public.p2
! -> Result
! Output: 2, 'xyzzy'::text
!
! \t off
INSERT INTO pt VALUES (2, 'xyzzy');
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7029,7034 **** NOTICE: drop cascades to foreign table bar2
--- 7029,7236 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo');
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ insert into pt values (1, 1), (2, 1);
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ remp | 2 | 1
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ (1 row)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ (1 row)
+
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ QUERY PLAN
+ --------------------------------------------------------------------------------
+ Insert on public.pt
+ Output: pt.a, pt.b
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2) RETURNING a, b
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (7 rows)
+
+ insert into pt values (1, 2), (2, 2) returning *;
+ a | b
+ ---+---
+ 1 | 2
+ 2 | 2
+ (2 rows)
+
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ remp | 2 | 1
+ remp | 2 | 2
+ (4 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ remp | 2 | 2
+ (2 rows)
+
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Insert on public.pt
+ Insert on public.locp
+ Foreign Insert on public.remp
+ Remote SQL: INSERT INTO public.locbar(a, b) VALUES ($1, $2)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+
+ execute q1;
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ locp | 1 | 3
+ remp | 2 | 1
+ remp | 2 | 2
+ remp | 2 | 3
+ (6 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ locp | 1 | 1
+ locp | 1 | 2
+ locp | 1 | 3
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+---
+ remp | 2 | 1
+ remp | 2 | 2
+ remp | 2 | 3
+ (3 rows)
+
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+ -- Check INSERT into a multi-level partitioned table
+ create table mlpt (a int, b int, c varchar) partition by range (a);
+ create table mlptp1 partition of mlpt for values from (100) to (200) partition by range(b);
+ create table mlptp2 partition of mlpt for values from (200) to (300);
+ create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 100 and b < 200), c varchar);
+ create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 200 and b < 300), c varchar);
+ create foreign table mlptp1p1 partition of mlptp1 for values from (100) to (200) server loopback options (table_name 'locfoo');
+ create foreign table mlptp1p2 partition of mlptp1 for values from (200) to (300) server loopback options (table_name 'locbar');
+ explain (verbose, costs off)
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ QUERY PLAN
+ ------------------------------------------------------------------------------------------
+ Insert on public.mlpt
+ Output: mlpt.a, mlpt.b, mlpt.c
+ Foreign Insert on public.mlptp1p1
+ Remote SQL: INSERT INTO public.locfoo(a, b, c) VALUES ($1, $2, $3) RETURNING a, b, c
+ Foreign Insert on public.mlptp1p2
+ Remote SQL: INSERT INTO public.locbar(a, b, c) VALUES ($1, $2, $3) RETURNING a, b, c
+ Insert on public.mlptp2
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2, "*VALUES*".column3
+ (9 rows)
+
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ a | b | c
+ -----+-----+---
+ 101 | 101 | x
+ 101 | 201 | y
+ (2 rows)
+
+ select tableoid::regclass, * FROM mlpt;
+ tableoid | a | b | c
+ ----------+-----+-----+---
+ mlptp1p1 | 101 | 101 | x
+ mlptp1p2 | 101 | 201 | y
+ (2 rows)
+
+ select tableoid::regclass, * FROM mlptp1;
+ tableoid | a | b | c
+ ----------+-----+-----+---
+ mlptp1p1 | 101 | 101 | x
+ mlptp1p2 | 101 | 201 | y
+ (2 rows)
+
+ select tableoid::regclass, * FROM mlptp2;
+ tableoid | a | b | c
+ ----------+---+---+---
+ (0 rows)
+
+ select tableoid::regclass, * FROM mlptp1p1;
+ tableoid | a | b | c
+ ----------+-----+-----+---
+ mlptp1p1 | 101 | 101 | x
+ (1 row)
+
+ select tableoid::regclass, * FROM mlptp1p2;
+ tableoid | a | b | c
+ ----------+-----+-----+---
+ mlptp1p2 | 101 | 201 | y
+ (1 row)
+
+ drop table mlpt;
+ drop table locfoo;
+ drop table locbar;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1662,1667 **** drop table loct1;
--- 1662,1730 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo');
+
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ insert into pt values (1, 1), (2, 1);
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ insert into pt values (1, 2), (2, 2) returning *;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ execute q1;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+
+ -- Check INSERT into a multi-level partitioned table
+ create table mlpt (a int, b int, c varchar) partition by range (a);
+ create table mlptp1 partition of mlpt for values from (100) to (200) partition by range(b);
+ create table mlptp2 partition of mlpt for values from (200) to (300);
+ create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 100 and b < 200), c varchar);
+ create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 200 and b < 300), c varchar);
+ create foreign table mlptp1p1 partition of mlptp1 for values from (100) to (200) server loopback options (table_name 'locfoo');
+ create foreign table mlptp1p2 partition of mlptp1 for values from (200) to (300) server loopback options (table_name 'locbar');
+
+ explain (verbose, costs off)
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+
+ select tableoid::regclass, * FROM mlpt;
+ select tableoid::regclass, * FROM mlptp1;
+ select tableoid::regclass, * FROM mlptp2;
+ select tableoid::regclass, * FROM mlptp1p1;
+ select tableoid::regclass, * FROM mlptp1p2;
+
+ drop table mlpt;
+ drop table locfoo;
+ drop table locbar;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2998,3008 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<sect3 id="ddl-partitioning-declarative-example">
--- 2998,3006 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<sect3 id="ddl-partitioning-declarative-example">
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2472,2487 **** CopyFrom(CopyState cstate)
if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionDispatch *partition_dispatch_info;
ResultRelInfo **partitions;
TupleConversionMap **partition_tupconv_maps;
TupleTableSlot *partition_tuple_slot;
int num_parted,
num_partitions;
ExecSetupPartitionTupleRouting(cstate->rel,
- 1,
- estate,
&partition_dispatch_info,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
--- 2472,2490 ----
if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionDispatch *partition_dispatch_info;
+ List *partition_oids;
ResultRelInfo **partitions;
TupleConversionMap **partition_tupconv_maps;
TupleTableSlot *partition_tuple_slot;
int num_parted,
num_partitions;
+ ResultRelInfo *partRelInfo;
+ int i;
+ ListCell *l;
ExecSetupPartitionTupleRouting(cstate->rel,
&partition_dispatch_info,
+ &partition_oids,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
***************
*** 2493,2498 **** CopyFrom(CopyState cstate)
--- 2496,2535 ----
cstate->partition_tupconv_maps = partition_tupconv_maps;
cstate->partition_tuple_slot = partition_tuple_slot;
+ partRelInfo = (ResultRelInfo *) palloc0(num_partitions *
+ sizeof(ResultRelInfo));
+ i = 0;
+ foreach(l, partition_oids)
+ {
+ Oid partOid = lfirst_oid(l);
+ Relation partrel;
+
+ /* Prepare ResultRelInfo and map for the partition */
+ ExecInitPartition(estate,
+ resultRelInfo,
+ partOid,
+ 0, /* dummy rangetable index */
+ partRelInfo,
+ &partition_tupconv_maps[i]);
+ partitions[i] = partRelInfo;
+
+ /* Verify the partition is a valid target for COPY */
+ partrel = partRelInfo->ri_RelationDesc;
+ if (partrel->rd_rel->relkind == RELKIND_RELATION)
+ partRelInfo->ri_PartitionIsValid = true;
+ else
+ {
+ /* The partition should be foreign */
+ Assert(partrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE);
+
+ /* We do not yet have a way to copy into a foreign partition */
+ partRelInfo->ri_PartitionIsValid = false;
+ }
+
+ partRelInfo++;
+ i++;
+ }
+
/*
* If we are capturing transition tuples, they may need to be
* converted from partition format back to partitioned table format
***************
*** 2641,2651 **** CopyFrom(CopyState cstate)
saved_resultRelInfo = resultRelInfo;
resultRelInfo = cstate->partitions[leaf_part_index];
! /* We do not yet have a way to insert into a foreign partition */
! if (resultRelInfo->ri_FdwRoutine)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
--- 2678,2693 ----
saved_resultRelInfo = resultRelInfo;
resultRelInfo = cstate->partitions[leaf_part_index];
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* The partition should be foreign */
! Assert(resultRelInfo->ri_FdwRoutine);
!
! /* We do not yet have a way to copy into a foreign partition */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route copied tuples to a foreign table")));
! }
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 117,122 **** static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
--- 117,126 ----
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
ExplainState *es);
+ static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ const char *operation, bool labeltarget,
+ bool main_target, int subplan_index, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
***************
*** 829,834 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 833,849 ----
if (((ModifyTable *) plan)->exclRelRTI)
*rels_used = bms_add_member(*rels_used,
((ModifyTable *) plan)->exclRelRTI);
+ if (((ModifyTable *) plan)->partition_rels)
+ {
+ ListCell *lc;
+
+ foreach(lc, ((ModifyTable *) plan)->partition_rels)
+ {
+ Index rti = lfirst_int(lc);
+
+ *rels_used = bms_add_member(*rels_used, rti);
+ }
+ }
break;
default:
break;
***************
*** 2889,2945 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! if (labeltargets)
! {
! /* Open a group for this target */
! ExplainOpenGroup("Target Table", NULL, true, es);
!
! /*
! * In text mode, decorate each target with operation type, so that
! * ExplainTargetRel's output of " on foo" will read nicely.
! */
! if (es->format == EXPLAIN_FORMAT_TEXT)
! {
! appendStringInfoSpaces(es->str, es->indent * 2);
! appendStringInfoString(es->str,
! fdwroutine ? foperation : operation);
! }
!
! /* Identify target */
! ExplainTargetRel((Plan *) node,
! resultRelInfo->ri_RangeTableIndex,
! es);
! if (es->format == EXPLAIN_FORMAT_TEXT)
! {
! appendStringInfoChar(es->str, '\n');
! es->indent++;
! }
! }
! /* Give FDW a chance if needed */
! if (!resultRelInfo->ri_usesFdwDirectModify &&
! fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
{
! List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
! fdwroutine->ExplainForeignModify(mtstate,
! resultRelInfo,
! fdw_private,
! j,
! es);
}
! if (labeltargets)
! {
! /* Undo the indentation we added in text format */
! if (es->format == EXPLAIN_FORMAT_TEXT)
! es->indent--;
!
! /* Close the group */
! ExplainCloseGroup("Target Table", NULL, true, es);
! }
}
/* Gather names of ON CONFLICT arbiter indexes */
--- 2904,2930 ----
ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! show_actual_target(mtstate, node, resultRelInfo, fdwroutine,
! fdwroutine ? foperation : operation,
! labeltargets, true, j, es);
! }
! /* Print partition tables if needed */
! if (mtstate->mt_num_partitions > 0)
! {
! ExplainOpenGroup("Partition Tables", "Partition Tables", false, es);
! for (j = 0; j < mtstate->mt_num_partitions; j++)
{
! ResultRelInfo *resultRelInfo = mtstate->mt_partitions[j];
! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! show_actual_target(mtstate, node, resultRelInfo, fdwroutine,
! fdwroutine ? foperation : operation,
! true, false, j, es);
}
! ExplainCloseGroup("Partition Tables", "Partition Tables", false, es);
}
/* Gather names of ON CONFLICT arbiter indexes */
***************
*** 2996,3001 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
--- 2981,3058 ----
}
/*
+ * Show an actual target relation
+ */
+ static void
+ show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ const char *operation, bool labeltarget,
+ bool main_target, int subplan_index, ExplainState *es)
+ {
+ if (labeltarget)
+ {
+ /* Open a group for this target */
+ if (main_target)
+ ExplainOpenGroup("Target Table", NULL, true, es);
+ else
+ ExplainOpenGroup("Partition Table", NULL, true, es);
+
+ /*
+ * In text mode, decorate each target with operation type, so that
+ * ExplainTargetRel's output of " on foo" will read nicely.
+ */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, operation);
+ }
+
+ /* Identify target */
+ ExplainTargetRel((Plan *) node,
+ resultRelInfo->ri_RangeTableIndex,
+ es);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoChar(es->str, '\n');
+ es->indent++;
+ }
+ }
+
+ /* Give FDW a chance if needed */
+ if (fdwroutine != NULL &&
+ fdwroutine->ExplainForeignModify != NULL &&
+ !resultRelInfo->ri_usesFdwDirectModify)
+ {
+ List *fdw_private;
+
+ if (main_target)
+ fdw_private = (List *) list_nth(node->fdwPrivLists, subplan_index);
+ else
+ fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, subplan_index);
+
+ fdwroutine->ExplainForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ main_target ? subplan_index : 0,
+ es);
+ }
+
+ if (labeltarget)
+ {
+ /* Undo the indentation we added in text format */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+
+ /* Close the group */
+ if (main_target)
+ ExplainCloseGroup("Target Table", NULL, true, es);
+ else
+ ExplainCloseGroup("Partition Table", NULL, true, es);
+ }
+ }
+
+ /*
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
* BitmapAnd, or BitmapOr node.
*
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1100,1111 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
--- 1100,1114 ----
Relation resultRel = resultRelInfo->ri_RelationDesc;
TriggerDesc *trigDesc = resultRel->trigdesc;
FdwRoutine *fdwroutine;
+ bool is_valid;
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
CheckCmdReplicaIdentity(resultRel, operation);
+ if (resultRelInfo->ri_PartitionRoot && operation == CMD_INSERT)
+ resultRelInfo->ri_PartitionIsValid = true;
break;
case RELKIND_SEQUENCE:
ereport(ERROR,
***************
*** 1172,1195 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
switch (operation)
{
case CMD_INSERT:
!
! /*
! * If foreign partition to do tuple-routing for, skip the
! * check; it's disallowed elsewhere.
! */
! if (resultRelInfo->ri_PartitionRoot)
! break;
if (fdwroutine->ExecForeignInsert == NULL)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot insert into foreign table \"%s\"",
! RelationGetRelationName(resultRel))));
if (fdwroutine->IsForeignRelUpdatable != NULL &&
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("foreign table \"%s\" does not allow inserts",
! RelationGetRelationName(resultRel))));
break;
case CMD_UPDATE:
if (fdwroutine->ExecForeignUpdate == NULL)
--- 1175,1202 ----
switch (operation)
{
case CMD_INSERT:
! is_valid = true;
if (fdwroutine->ExecForeignInsert == NULL)
! {
! if (!resultRelInfo->ri_PartitionRoot)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot insert into foreign table \"%s\"",
! RelationGetRelationName(resultRel))));
! is_valid = false;
! }
if (fdwroutine->IsForeignRelUpdatable != NULL &&
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
! {
! if (!resultRelInfo->ri_PartitionRoot)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("foreign table \"%s\" does not allow inserts",
! RelationGetRelationName(resultRel))));
! is_valid = false;
! }
! if (resultRelInfo->ri_PartitionRoot)
! resultRelInfo->ri_PartitionIsValid = is_valid;
break;
case CMD_UPDATE:
if (fdwroutine->ExecForeignUpdate == NULL)
***************
*** 1306,1312 **** void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! Relation partition_root,
int instrument_options)
{
List *partition_check = NIL;
--- 1313,1319 ----
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! ResultRelInfo *partition_root,
int instrument_options)
{
List *partition_check = NIL;
***************
*** 1362,1369 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
*/
partition_check = RelationGetPartitionQual(resultRelationDesc);
- resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
}
/*
--- 1369,1377 ----
*/
partition_check = RelationGetPartitionQual(resultRelationDesc);
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionCheck = partition_check;
+ resultRelInfo->ri_PartitionIsValid = false;
}
/*
***************
*** 1890,1904 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
{
char *val_desc;
Relation orig_rel = rel;
/* See the comment above. */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 1898,1913 ----
{
char *val_desc;
Relation orig_rel = rel;
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/* See the comment above. */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 1909,1918 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 1918,1932 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 1964,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1978,1984 ----
char *val_desc;
Relation orig_rel = rel;
TupleDesc orig_tupdesc = RelationGetDescr(rel);
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/*
* If the tuple has been routed, it's been converted to the
***************
*** 1972,1983 **** ExecConstraints(ResultRelInfo *resultRelInfo,
* rowtype so that val_desc shown error message matches the
* input tuple.
*/
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(orig_tupdesc, tupdesc,
--- 1987,1998 ----
* rowtype so that val_desc shown error message matches the
* input tuple.
*/
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(orig_tupdesc, tupdesc,
***************
*** 1988,1997 **** ExecConstraints(ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2003,2017 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 2017,2031 **** ExecConstraints(ResultRelInfo *resultRelInfo,
{
char *val_desc;
Relation orig_rel = rel;
/* See the comment above. */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 2037,2052 ----
{
char *val_desc;
Relation orig_rel = rel;
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/* See the comment above. */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2036,2045 **** ExecConstraints(ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2057,2071 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 2111,2116 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
--- 2137,2143 ----
*/
if (!ExecQual(wcoExpr, econtext))
{
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
char *val_desc;
Bitmapset *modifiedCols;
Bitmapset *insertedCols;
***************
*** 2129,2141 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
*/
case WCO_VIEW_CHECK:
/* See the comment in ExecConstraints(). */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 2156,2168 ----
*/
case WCO_VIEW_CHECK:
/* See the comment in ExecConstraints(). */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2146,2155 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2173,2187 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 44,49 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
--- 44,51 ----
* Output arguments:
* 'pd' receives an array of PartitionDispatch objects with one entry for
* every partitioned table in the partition tree
+ * 'leaf_parts' receives a list of relation OIDs with one entry for every leaf
+ * partition in the partition tree
* 'partitions' receives an array of ResultRelInfo* objects with one entry for
* every leaf partition in the partition tree
* 'tup_conv_maps' receives an array of TupleConversionMap objects with one
***************
*** 64,90 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
*/
void
ExecSetupPartitionTupleRouting(Relation rel,
- Index resultRTindex,
- EState *estate,
PartitionDispatch **pd,
ResultRelInfo ***partitions,
TupleConversionMap ***tup_conv_maps,
TupleTableSlot **partition_tuple_slot,
int *num_parted, int *num_partitions)
{
- TupleDesc tupDesc = RelationGetDescr(rel);
- List *leaf_parts;
- ListCell *cell;
- int i;
- ResultRelInfo *leaf_part_rri;
-
/*
* Get the information about the partition tree after locking all the
* partitions.
*/
(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
! *pd = RelationGetPartitionDispatchInfo(rel, num_parted, &leaf_parts);
! *num_partitions = list_length(leaf_parts);
*partitions = (ResultRelInfo **) palloc(*num_partitions *
sizeof(ResultRelInfo *));
*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
--- 66,85 ----
*/
void
ExecSetupPartitionTupleRouting(Relation rel,
PartitionDispatch **pd,
+ List **leaf_parts,
ResultRelInfo ***partitions,
TupleConversionMap ***tup_conv_maps,
TupleTableSlot **partition_tuple_slot,
int *num_parted, int *num_partitions)
{
/*
* Get the information about the partition tree after locking all the
* partitions.
*/
(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
! *pd = RelationGetPartitionDispatchInfo(rel, num_parted, leaf_parts);
! *num_partitions = list_length(*leaf_parts);
*partitions = (ResultRelInfo **) palloc(*num_partitions *
sizeof(ResultRelInfo *));
*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
***************
*** 97,152 **** ExecSetupPartitionTupleRouting(Relation rel,
* processing.
*/
*partition_tuple_slot = MakeTupleTableSlot();
! leaf_part_rri = (ResultRelInfo *) palloc0(*num_partitions *
! sizeof(ResultRelInfo));
! i = 0;
! foreach(cell, leaf_parts)
! {
! Relation partrel;
! TupleDesc part_tupdesc;
!
! /*
! * We locked all the partitions above including the leaf partitions.
! * Note that each of the relations in *partitions are eventually
! * closed by the caller.
! */
! partrel = heap_open(lfirst_oid(cell), NoLock);
! part_tupdesc = RelationGetDescr(partrel);
!
! /*
! * Save a tuple conversion map to convert a tuple routed to this
! * partition from the parent's type to the partition's.
! */
! (*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
! gettext_noop("could not convert row type"));
! InitResultRelInfo(leaf_part_rri,
! partrel,
! resultRTindex,
! rel,
! estate->es_instrument);
! /*
! * Verify result relation is a valid target for INSERT.
! */
! CheckValidResultRel(leaf_part_rri, CMD_INSERT);
! /*
! * Open partition indices (remember we do not support ON CONFLICT in
! * case of partitioned tables, so we do not need support information
! * for speculative insertion)
! */
! if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
! leaf_part_rri->ri_IndexRelationDescs == NULL)
! ExecOpenIndices(leaf_part_rri, false);
! estate->es_leaf_result_relations =
! lappend(estate->es_leaf_result_relations, leaf_part_rri);
! (*partitions)[i] = leaf_part_rri++;
! i++;
! }
}
/*
--- 92,148 ----
* processing.
*/
*partition_tuple_slot = MakeTupleTableSlot();
+ }
! /*
! * ExecInitPartition -- Prepare ResultRelInfo and tuple conversion map for
! * the partition with OID 'partOid'
! */
! void
! ExecInitPartition(EState *estate,
! ResultRelInfo *rootRelInfo,
! Oid partOid,
! Index partRTindex,
! ResultRelInfo *partRelInfo,
! TupleConversionMap **partTupConvMap)
! {
! Relation rootrel = rootRelInfo->ri_RelationDesc;
! Relation partrel;
! /*
! * We assume that ExecSetupPartitionTupleRouting() already locked the
! * partition, so we need no lock here. The partition must be closed
! * by the caller.
! */
! partrel = heap_open(partOid, NoLock);
! /* Save ResultRelInfo data for the partition. */
! InitResultRelInfo(partRelInfo,
! partrel,
! partRTindex,
! rootRelInfo,
! estate->es_instrument);
! /*
! * Open partition indices (remember we do not support ON CONFLICT in
! * case of partitioned tables, so we do not need support information
! * for speculative insertion)
! */
! if (partRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
! partRelInfo->ri_IndexRelationDescs == NULL)
! ExecOpenIndices(partRelInfo, false);
! /* Store ResultRelInfo in *estate. */
! estate->es_leaf_result_relations =
! lappend(estate->es_leaf_result_relations, partRelInfo);
! /*
! * Save conversion map to convert a tuple routed to the partition from
! * the parent's type to the partition's.
! */
! *partTupConvMap = convert_tuples_by_name(RelationGetDescr(rootrel),
! RelationGetDescr(partrel),
! gettext_noop("could not convert row type"));
}
/*
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 306,316 **** ExecInsert(ModifyTableState *mtstate,
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions[leaf_part_index];
! /* We do not yet have a way to insert into a foreign partition */
! if (resultRelInfo->ri_FdwRoutine)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
--- 306,322 ----
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions[leaf_part_index];
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* The partition should be foreign */
! Assert(resultRelInfo->ri_FdwRoutine);
!
! /* We cannot insert into this foreign partition */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to foreign table \"%s\"",
! RelationGetRelationName(resultRelInfo->ri_RelationDesc))));
! }
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
***************
*** 1946,1961 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionDispatch *partition_dispatch_info;
ResultRelInfo **partitions;
TupleConversionMap **partition_tupconv_maps;
TupleTableSlot *partition_tuple_slot;
int num_parted,
num_partitions;
ExecSetupPartitionTupleRouting(rel,
- node->nominalRelation,
- estate,
&partition_dispatch_info,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
--- 1952,1968 ----
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionDispatch *partition_dispatch_info;
+ List *partition_oids;
ResultRelInfo **partitions;
TupleConversionMap **partition_tupconv_maps;
TupleTableSlot *partition_tuple_slot;
int num_parted,
num_partitions;
+ ResultRelInfo *partRelInfo;
ExecSetupPartitionTupleRouting(rel,
&partition_dispatch_info,
+ &partition_oids,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
***************
*** 1966,1971 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1973,2016 ----
mtstate->mt_num_partitions = num_partitions;
mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
mtstate->mt_partition_tuple_slot = partition_tuple_slot;
+
+ partRelInfo = (ResultRelInfo *) palloc0(num_partitions *
+ sizeof(ResultRelInfo));
+ i = 0;
+ foreach(l, partition_oids)
+ {
+ Oid partOid = lfirst_oid(l);
+ Index partRTindex = list_nth_int(node->partition_rels, i);
+
+ /* Prepare ResultRelInfo and map for the partition */
+ ExecInitPartition(estate,
+ mtstate->resultRelInfo,
+ partOid,
+ partRTindex,
+ partRelInfo,
+ &partition_tupconv_maps[i]);
+ partitions[i] = partRelInfo;
+
+ /* Verify the partition is a valid target for INSERT */
+ CheckValidResultRel(partRelInfo, CMD_INSERT);
+
+ /* If so, allow the FDW to init itself for the partition */
+ if (partRelInfo->ri_PartitionIsValid &&
+ partRelInfo->ri_FdwRoutine != NULL &&
+ partRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, i);
+
+ partRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ partRelInfo,
+ fdw_private,
+ 0,
+ eflags);
+ }
+
+ partRelInfo++;
+ i++;
+ }
}
/*
***************
*** 2391,2396 **** ExecEndModifyTable(ModifyTableState *node)
--- 2436,2446 ----
{
ResultRelInfo *resultRelInfo = node->mt_partitions[i];
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+ resultRelInfo);
+
ExecCloseIndices(resultRelInfo);
heap_close(resultRelInfo->ri_RelationDesc, NoLock);
}
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 204,209 **** _copyModifyTable(const ModifyTable *from)
--- 204,210 ----
COPY_SCALAR_FIELD(canSetTag);
COPY_SCALAR_FIELD(nominalRelation);
COPY_NODE_FIELD(partitioned_rels);
+ COPY_NODE_FIELD(partition_rels);
COPY_NODE_FIELD(resultRelations);
COPY_SCALAR_FIELD(resultRelIndex);
COPY_SCALAR_FIELD(rootResultRelIndex);
***************
*** 220,225 **** _copyModifyTable(const ModifyTable *from)
--- 221,227 ----
COPY_NODE_FIELD(onConflictWhere);
COPY_SCALAR_FIELD(exclRelRTI);
COPY_NODE_FIELD(exclRelTlist);
+ COPY_NODE_FIELD(fdwPartitionPrivLists);
return newnode;
}
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 372,377 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 372,378 ----
WRITE_BOOL_FIELD(canSetTag);
WRITE_UINT_FIELD(nominalRelation);
WRITE_NODE_FIELD(partitioned_rels);
+ WRITE_NODE_FIELD(partition_rels);
WRITE_NODE_FIELD(resultRelations);
WRITE_INT_FIELD(resultRelIndex);
WRITE_INT_FIELD(rootResultRelIndex);
***************
*** 388,393 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 389,395 ----
WRITE_NODE_FIELD(onConflictWhere);
WRITE_UINT_FIELD(exclRelRTI);
WRITE_NODE_FIELD(exclRelTlist);
+ WRITE_NODE_FIELD(fdwPartitionPrivLists);
}
static void
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1568,1573 **** _readModifyTable(void)
--- 1568,1574 ----
READ_BOOL_FIELD(canSetTag);
READ_UINT_FIELD(nominalRelation);
READ_NODE_FIELD(partitioned_rels);
+ READ_NODE_FIELD(partition_rels);
READ_NODE_FIELD(resultRelations);
READ_INT_FIELD(resultRelIndex);
READ_INT_FIELD(rootResultRelIndex);
***************
*** 1584,1589 **** _readModifyTable(void)
--- 1585,1591 ----
READ_NODE_FIELD(onConflictWhere);
READ_UINT_FIELD(exclRelRTI);
READ_NODE_FIELD(exclRelTlist);
+ READ_NODE_FIELD(fdwPartitionPrivLists);
READ_DONE();
}
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 35,40 ****
--- 35,41 ----
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/predtest.h"
+ #include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
***************
*** 6435,6440 **** make_modifytable(PlannerInfo *root,
--- 6436,6442 ----
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
Bitmapset *direct_modify_plans;
+ List *partition_rels;
ListCell *lc;
int i;
***************
*** 6558,6563 **** make_modifytable(PlannerInfo *root,
--- 6560,6695 ----
node->fdwPrivLists = fdw_private_list;
node->fdwDirectModifyPlans = direct_modify_plans;
+ /*
+ * Also, if this is an INSERT into a partitioned table, build a list of
+ * RT indexes of partitions, and for each foreign partition, allow the FDW
+ * to construct private plan data and accumulate it all into another list.
+ *
+ * Note: ExecSetupPartitionTupleRouting() will expand partitions in the
+ * same order as these lists.
+ */
+ partition_rels = NIL;
+ fdw_private_list = NIL;
+ if (operation == CMD_INSERT &&
+ planner_rt_fetch(nominalRelation, root)->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *saved_withCheckOptionLists = node->withCheckOptionLists;
+ List *saved_returningLists = node->returningLists;
+ Plan *subplan = (Plan *) linitial(node->plans);
+ List *saved_tlist = subplan->targetlist;
+ Query **parent_parses;
+ Query *parent_parse;
+ Bitmapset *parent_relids;
+
+ /*
+ * Similarly to inheritance_planner(), we generate a modified query
+ * with each child as target by applying adjust_appendrel_attrs() to
+ * the query for its immediate parent, so build an array to store in
+ * the query for each parent.
+ */
+ parent_parses = (Query **)
+ palloc0((list_length(root->parse->rtable) + 1) * sizeof(Query *));
+
+ parent_parses[nominalRelation] = root->parse;
+ parent_relids = bms_make_singleton(nominalRelation);
+
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ Index parent_rti = appinfo->parent_relid;
+ Index child_rti = appinfo->child_relid;
+ RangeTblEntry *child_rte;
+ Query *child_parse;
+ FdwRoutine *fdwroutine = NULL;
+ List *fdw_private = NIL;
+
+ /* append_rel_list contains all append rels; ignore others */
+ if (!bms_is_member(parent_rti, parent_relids))
+ continue;
+
+ child_rte = planner_rt_fetch(child_rti, root);
+ Assert(child_rte->rtekind == RTE_RELATION);
+
+ /* No work if the child is a plain table */
+ if (child_rte->relkind == RELKIND_RELATION)
+ {
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list, NIL);
+ continue;
+ }
+
+ /*
+ * expand_inherited_rtentry() always processes a parent before any
+ * of that parent's children, so the query for its parent should
+ * already be available.
+ */
+ parent_parse = parent_parses[parent_rti];
+ Assert(parent_parse);
+
+ /* Generate the query with the child as target */
+ child_parse = (Query *)
+ adjust_appendrel_attrs(root,
+ (Node *) parent_parse,
+ 1, &appinfo);
+
+ /* Store the query if the child is a partitioned table */
+ if (child_rte->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ parent_parses[child_rti] = child_parse;
+ parent_relids = bms_add_member(parent_relids, child_rti);
+ continue;
+ }
+
+ /* The child should be a foreign table */
+ Assert(child_rte->relkind == RELKIND_FOREIGN_TABLE);
+
+ fdwroutine = GetFdwRoutineByRelId(child_rte->relid);
+ Assert(fdwroutine != NULL);
+
+ if (fdwroutine->PlanForeignModify != NULL)
+ {
+ List *tlist;
+
+ /* Replace the root query with the child query. */
+ root->parse = child_parse;
+
+ /* Adjust the plan node to refer to the child as target. */
+ node->nominalRelation = child_rti;
+ node->resultRelations = list_make1_int(child_rti);
+ node->withCheckOptionLists =
+ list_make1(child_parse->withCheckOptions);
+ node->returningLists =
+ list_make1(child_parse->returningList);
+
+ /*
+ * The column list of the child might have a different column
+ * order and/or a different set of dropped columns than that
+ * of its parent, so adjust the subplan's tlist as well.
+ */
+ tlist = preprocess_targetlist(root,
+ child_parse->targetList);
+ subplan->targetlist = tlist;
+
+ fdw_private = fdwroutine->PlanForeignModify(root,
+ node,
+ child_rti,
+ 0);
+
+ subplan->targetlist = saved_tlist;
+ node->nominalRelation = nominalRelation;
+ node->resultRelations = list_make1_int(nominalRelation);
+ node->withCheckOptionLists = saved_withCheckOptionLists;
+ node->returningLists = saved_returningLists;
+ root->parse = parent_parses[nominalRelation];
+ }
+
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list, fdw_private);
+ }
+ }
+ node->partition_rels = partition_rels;
+ node->fdwPartitionPrivLists = fdw_private_list;
+
return node;
}
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 879,888 **** subquery_planner(PlannerGlobal *glob, Query *parse,
reduce_outer_joins(root);
/*
! * Do the main planning. If we have an inherited target relation, that
! * needs special processing, else go straight to grouping_planner.
*/
! if (parse->resultRelation &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
--- 879,890 ----
reduce_outer_joins(root);
/*
! * Do the main planning. If we have an inherited UPDATE/DELETE target
! * relation, that needs special processing, else go straight to
! * grouping_planner.
*/
! if ((parse->commandType == CMD_UPDATE ||
! parse->commandType == CMD_DELETE) &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 858,863 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 858,867 ----
{
lfirst_int(l) += rtoffset;
}
+ foreach(l, splan->partition_rels)
+ {
+ lfirst_int(l) += rtoffset;
+ }
foreach(l, splan->resultRelations)
{
lfirst_int(l) += rtoffset;
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1974,1981 **** adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
if (newnode->resultRelation == appinfo->parent_relid)
{
newnode->resultRelation = appinfo->child_relid;
! /* Fix tlist resnos too, if it's inherited UPDATE */
! if (newnode->commandType == CMD_UPDATE)
newnode->targetList =
adjust_inherited_tlist(newnode->targetList,
appinfo);
--- 1974,1982 ----
if (newnode->resultRelation == appinfo->parent_relid)
{
newnode->resultRelation = appinfo->child_relid;
! /* Fix tlist resnos too, if it's inherited INSERT/UPDATE */
! if (newnode->commandType == CMD_INSERT ||
! newnode->commandType == CMD_UPDATE)
newnode->targetList =
adjust_inherited_tlist(newnode->targetList,
appinfo);
***************
*** 2325,2331 **** adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
}
/*
! * Adjust the targetlist entries of an inherited UPDATE operation
*
* The expressions have already been fixed, but we have to make sure that
* the target resnos match the child table (they may not, in the case of
--- 2326,2332 ----
}
/*
! * Adjust the targetlist entries of an inherited INSERT/UPDATE operation
*
* The expressions have already been fixed, but we have to make sure that
* the target resnos match the child table (they may not, in the case of
***************
*** 2337,2344 **** adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
* The given tlist has already been through expression_tree_mutator;
* therefore the TargetEntry nodes are fresh copies that it's okay to
* scribble on.
- *
- * Note that this is not needed for INSERT because INSERT isn't inheritable.
*/
static List *
adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
--- 2338,2343 ----
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 542,547 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 542,552 ----
qry->resultRelation = setTargetTable(pstate, stmt->relation,
false, false, targetPerms);
+ /* Set the inh flag to true if the target table is partitioned */
+ rte = pstate->p_target_rangetblentry;
+ if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+ rte->inh = true;
+
/* Validate stmt->cols list, or build default list if no list given */
icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
Assert(list_length(icolumns) == list_length(attrnos));
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 2890,2902 **** rewriteTargetView(Query *parsetree, Relation view)
new_rt_index = list_length(parsetree->rtable);
/*
- * INSERTs never inherit. For UPDATE/DELETE, we use the view query's
- * inheritance flag for the base relation.
- */
- if (parsetree->commandType == CMD_INSERT)
- new_rte->inh = false;
-
- /*
* Adjust the view's targetlist Vars to reference the new target RTE, ie
* make their varnos be new_rt_index instead of base_rt_index. There can
* be no Vars for other rels in the tlist, so this is sufficient to pull
--- 2890,2895 ----
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 50,62 **** typedef struct PartitionDispatchData
typedef struct PartitionDispatchData *PartitionDispatch;
extern void ExecSetupPartitionTupleRouting(Relation rel,
- Index resultRTindex,
- EState *estate,
PartitionDispatch **pd,
ResultRelInfo ***partitions,
TupleConversionMap ***tup_conv_maps,
TupleTableSlot **partition_tuple_slot,
int *num_parted, int *num_partitions);
extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
PartitionDispatch *pd,
TupleTableSlot *slot,
--- 50,67 ----
typedef struct PartitionDispatchData *PartitionDispatch;
extern void ExecSetupPartitionTupleRouting(Relation rel,
PartitionDispatch **pd,
+ List **leaf_parts,
ResultRelInfo ***partitions,
TupleConversionMap ***tup_conv_maps,
TupleTableSlot **partition_tuple_slot,
int *num_parted, int *num_partitions);
+ extern void ExecInitPartition(EState *estate,
+ ResultRelInfo *rootRelInfo,
+ Oid partOid,
+ Index partRTindex,
+ ResultRelInfo *partRelInfo,
+ TupleConversionMap **partTupConvMap);
extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
PartitionDispatch *pd,
TupleTableSlot *slot,
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 181,187 **** extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! Relation partition_root,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecCleanUpTriggerState(EState *estate);
--- 181,187 ----
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! ResultRelInfo *partition_root,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecCleanUpTriggerState(EState *estate);
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 404,417 **** typedef struct ResultRelInfo
/* list of ON CONFLICT DO UPDATE exprs (qual) */
ExprState *ri_onConflictSetWhere;
/* partition check expression */
List *ri_PartitionCheck;
/* partition check expression state */
ExprState *ri_PartitionCheckExpr;
! /* relation descriptor for root partitioned table */
! Relation ri_PartitionRoot;
} ResultRelInfo;
/* ----------------
--- 404,420 ----
/* list of ON CONFLICT DO UPDATE exprs (qual) */
ExprState *ri_onConflictSetWhere;
+ /* root partitioned table */
+ struct ResultRelInfo *ri_PartitionRoot;
+
/* partition check expression */
List *ri_PartitionCheck;
/* partition check expression state */
ExprState *ri_PartitionCheckExpr;
! /* true when partition is legal for tuple-routing */
! bool ri_PartitionIsValid;
} ResultRelInfo;
/* ----------------
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 219,224 **** typedef struct ModifyTable
--- 219,226 ----
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
/* RT indexes of non-leaf tables in a partition tree */
List *partitioned_rels;
+ List *partition_rels; /* RT indexes of leaf tables in a partition
+ * tree */
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in plan's list */
int rootResultRelIndex; /* index of the partitioned table root */
***************
*** 235,240 **** typedef struct ModifyTable
--- 237,244 ----
Node *onConflictWhere; /* WHERE for ON CONFLICT UPDATE */
Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */
List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
+ List *fdwPartitionPrivLists; /* per-partition FDW private data
+ * lists */
} ModifyTable;
/* ----------------
On Fri, Nov 24, 2017 at 10:03 PM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
(2017/10/27 20:00), Etsuro Fujita wrote:
Please find attached an updated version of the patch.
Amit rebased this patch and sent me the rebased version off list. Thanks for
that, Amit!One thing I noticed I overlooked is about this change I added to
make_modifytable to make a valid-looking plan node to pass to
PlanForeignModify to plan remote insert to each foreign partition:+ /* + * The column list of the child might have a different column + * order and/or a different set of dropped columns than that + * of its parent, so adjust the subplan's tlist as well. + */ + tlist = preprocess_targetlist(root, + child_parse->targetList);This would be needed because the FDW might reference the tlist. Since
preprocess_targetlist references root->parse, it's needed to replace that
with the child query before calling that function, but I forgot to do that.
So I fixed that. Attached is an updated version of the patch.
Moved to next CF as this got no reviews and the last version is fresh.
--
Michael
Hi, Fujita-san!
On 24.11.2017 16:03, Etsuro Fujita wrote:
(2017/10/27 20:00), Etsuro Fujita wrote:
Please find attached an updated version of the patch.
Amit rebased this patch and sent me the rebased version off list.
Thanks for that, Amit!One thing I noticed I overlooked is about this change I added to
make_modifytable to make a valid-looking plan node to pass to
PlanForeignModify to plan remote insert to each foreign partition:+ /* + * The column list of the child might have a different column + * order and/or a different set of dropped columns than that + * of its parent, so adjust the subplan's tlist as well. + */ + tlist = preprocess_targetlist(root, + child_parse->targetList);This would be needed because the FDW might reference the tlist. Since
preprocess_targetlist references root->parse, it's needed to replace
that with the child query before calling that function, but I forgot
to do that. So I fixed that. Attached is an updated version of the
patch.
Your patch already is not applied on master. Please rebase it.
--
Regards,
Maksim Milyutin
Hi Maksim,
(2017/12/12 17:57), Maksim Milyutin wrote:
Your patch already is not applied on master. Please rebase it.
Done. Please find attached an updated version of the patch.
Best regards,
Etsuro Fujita
Attachments:
tuple-routing-to-foreign-partitions-v6.patchtext/x-diff; name=tuple-routing-to-foreign-partitions-v6.patchDownload
*** 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
***************
*** 7088,7093 **** NOTICE: drop cascades to foreign table bar2
--- 7088,7295 ----
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
***************
*** 1680,1685 **** drop table loct1;
--- 1680,1748 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo');
+
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ insert into pt values (1, 1), (2, 1);
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ insert into pt values (1, 2), (2, 2) returning *;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ execute q1;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+
+ -- Check INSERT into a multi-level partitioned table
+ create table mlpt (a int, b int, c varchar) partition by range (a);
+ create table mlptp1 partition of mlpt for values from (100) to (200) partition by range(b);
+ create table mlptp2 partition of mlpt for values from (200) to (300);
+ create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 100 and b < 200), c varchar);
+ create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 200 and b < 300), c varchar);
+ create foreign table mlptp1p1 partition of mlptp1 for values from (100) to (200) server loopback options (table_name 'locfoo');
+ create foreign table mlptp1p2 partition of mlptp1 for values from (200) to (300) server loopback options (table_name 'locbar');
+
+ explain (verbose, costs off)
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+
+ select tableoid::regclass, * FROM mlpt;
+ select tableoid::regclass, * FROM mlptp1;
+ select tableoid::regclass, * FROM mlptp2;
+ select tableoid::regclass, * FROM mlptp1p1;
+ select tableoid::regclass, * FROM mlptp1p2;
+
+ drop table mlpt;
+ drop table locfoo;
+ drop table locbar;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2998,3008 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<sect3 id="ddl-partitioning-declarative-example">
--- 2998,3006 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<sect3 id="ddl-partitioning-declarative-example">
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2473,2489 **** 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(NULL,
! cstate->rel,
! 1,
! estate,
&partition_dispatch_info,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
--- 2473,2491 ----
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,
***************
*** 2495,2500 **** CopyFrom(CopyState cstate)
--- 2497,2537 ----
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(NULL,
+ 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
***************
*** 2643,2653 **** 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
--- 2680,2695 ----
saved_resultRelInfo = resultRelInfo;
resultRelInfo = cstate->partitions[leaf_part_index];
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* The partition should be foreign */
! Assert(resultRelInfo->ri_FdwRoutine);
!
! /* We do not yet have a way to copy into a foreign partition */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route copied tuples to a foreign table")));
! }
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 117,122 **** static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
--- 117,126 ----
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
ExplainState *es);
+ static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ const char *operation, bool labeltarget,
+ bool main_target, int subplan_index, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
***************
*** 829,834 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 833,849 ----
if (((ModifyTable *) plan)->exclRelRTI)
*rels_used = bms_add_member(*rels_used,
((ModifyTable *) plan)->exclRelRTI);
+ if (((ModifyTable *) plan)->partition_rels)
+ {
+ ListCell *lc;
+
+ foreach(lc, ((ModifyTable *) plan)->partition_rels)
+ {
+ Index rti = lfirst_int(lc);
+
+ *rels_used = bms_add_member(*rels_used, rti);
+ }
+ }
break;
default:
break;
***************
*** 2917,2973 **** 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 */
--- 2932,2958 ----
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 */
***************
*** 3024,3029 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
--- 3009,3086 ----
}
/*
+ * Show an actual target relation
+ */
+ static void
+ show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ const char *operation, bool labeltarget,
+ bool main_target, int subplan_index, ExplainState *es)
+ {
+ if (labeltarget)
+ {
+ /* Open a group for this target */
+ if (main_target)
+ ExplainOpenGroup("Target Table", NULL, true, es);
+ else
+ ExplainOpenGroup("Partition Table", NULL, true, es);
+
+ /*
+ * In text mode, decorate each target with operation type, so that
+ * ExplainTargetRel's output of " on foo" will read nicely.
+ */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, operation);
+ }
+
+ /* Identify target */
+ ExplainTargetRel((Plan *) node,
+ resultRelInfo->ri_RangeTableIndex,
+ es);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoChar(es->str, '\n');
+ es->indent++;
+ }
+ }
+
+ /* Give FDW a chance if needed */
+ if (fdwroutine != NULL &&
+ fdwroutine->ExplainForeignModify != NULL &&
+ !resultRelInfo->ri_usesFdwDirectModify)
+ {
+ List *fdw_private;
+
+ if (main_target)
+ fdw_private = (List *) list_nth(node->fdwPrivLists, subplan_index);
+ else
+ fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, subplan_index);
+
+ fdwroutine->ExplainForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ main_target ? subplan_index : 0,
+ es);
+ }
+
+ if (labeltarget)
+ {
+ /* Undo the indentation we added in text format */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+
+ /* Close the group */
+ if (main_target)
+ ExplainCloseGroup("Target Table", NULL, true, es);
+ else
+ ExplainCloseGroup("Partition Table", NULL, true, es);
+ }
+ }
+
+ /*
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
* BitmapAnd, or BitmapOr node.
*
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1100,1111 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
--- 1100,1114 ----
Relation resultRel = resultRelInfo->ri_RelationDesc;
TriggerDesc *trigDesc = resultRel->trigdesc;
FdwRoutine *fdwroutine;
+ bool is_valid;
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
CheckCmdReplicaIdentity(resultRel, operation);
+ if (resultRelInfo->ri_PartitionRoot && operation == CMD_INSERT)
+ resultRelInfo->ri_PartitionIsValid = true;
break;
case RELKIND_SEQUENCE:
ereport(ERROR,
***************
*** 1172,1195 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
switch (operation)
{
case CMD_INSERT:
!
! /*
! * If foreign partition to do tuple-routing for, skip the
! * check; it's disallowed elsewhere.
! */
! if (resultRelInfo->ri_PartitionRoot)
! break;
if (fdwroutine->ExecForeignInsert == NULL)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot insert into foreign table \"%s\"",
! RelationGetRelationName(resultRel))));
if (fdwroutine->IsForeignRelUpdatable != NULL &&
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("foreign table \"%s\" does not allow inserts",
! RelationGetRelationName(resultRel))));
break;
case CMD_UPDATE:
if (fdwroutine->ExecForeignUpdate == NULL)
--- 1175,1202 ----
switch (operation)
{
case CMD_INSERT:
! is_valid = true;
if (fdwroutine->ExecForeignInsert == NULL)
! {
! if (!resultRelInfo->ri_PartitionRoot)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot insert into foreign table \"%s\"",
! RelationGetRelationName(resultRel))));
! is_valid = false;
! }
if (fdwroutine->IsForeignRelUpdatable != NULL &&
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
! {
! if (!resultRelInfo->ri_PartitionRoot)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("foreign table \"%s\" does not allow inserts",
! RelationGetRelationName(resultRel))));
! is_valid = false;
! }
! if (resultRelInfo->ri_PartitionRoot)
! resultRelInfo->ri_PartitionIsValid = is_valid;
break;
case CMD_UPDATE:
if (fdwroutine->ExecForeignUpdate == NULL)
***************
*** 1306,1312 **** void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! Relation partition_root,
int instrument_options)
{
List *partition_check = NIL;
--- 1313,1319 ----
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! ResultRelInfo *partition_root,
int instrument_options)
{
List *partition_check = NIL;
***************
*** 1362,1369 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
*/
partition_check = RelationGetPartitionQual(resultRelationDesc);
- resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
}
/*
--- 1369,1377 ----
*/
partition_check = RelationGetPartitionQual(resultRelationDesc);
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionCheck = partition_check;
+ resultRelInfo->ri_PartitionIsValid = false;
}
/*
***************
*** 1890,1904 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
{
char *val_desc;
Relation orig_rel = rel;
/* See the comment above. */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 1898,1913 ----
{
char *val_desc;
Relation orig_rel = rel;
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/* See the comment above. */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 1909,1918 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 1918,1932 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 1964,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1978,1984 ----
char *val_desc;
Relation orig_rel = rel;
TupleDesc orig_tupdesc = RelationGetDescr(rel);
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/*
* If the tuple has been routed, it's been converted to the
***************
*** 1972,1983 **** ExecConstraints(ResultRelInfo *resultRelInfo,
* rowtype so that val_desc shown error message matches the
* input tuple.
*/
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(orig_tupdesc, tupdesc,
--- 1987,1998 ----
* rowtype so that val_desc shown error message matches the
* input tuple.
*/
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(orig_tupdesc, tupdesc,
***************
*** 1988,1997 **** ExecConstraints(ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2003,2017 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 2017,2031 **** ExecConstraints(ResultRelInfo *resultRelInfo,
{
char *val_desc;
Relation orig_rel = rel;
/* See the comment above. */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 2037,2052 ----
{
char *val_desc;
Relation orig_rel = rel;
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/* See the comment above. */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2036,2045 **** ExecConstraints(ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2057,2071 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 2111,2116 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
--- 2137,2143 ----
*/
if (!ExecQual(wcoExpr, econtext))
{
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
char *val_desc;
Bitmapset *modifiedCols;
Bitmapset *insertedCols;
***************
*** 2129,2141 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
*/
case WCO_VIEW_CHECK:
/* See the comment in ExecConstraints(). */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 2156,2168 ----
*/
case WCO_VIEW_CHECK:
/* See the comment in ExecConstraints(). */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2146,2155 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2173,2187 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 44,49 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
--- 44,51 ----
* Output arguments:
* 'pd' receives an array of PartitionDispatch objects with one entry for
* every partitioned table in the partition tree
+ * 'leaf_parts' receives a list of relation OIDs with one entry for every leaf
+ * partition in the partition tree
* 'partitions' receives an array of ResultRelInfo* objects with one entry for
* every leaf partition in the partition tree
* 'tup_conv_maps' receives an array of TupleConversionMap objects with one
***************
*** 63,91 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
* RowExclusiveLock mode upon return from this function.
*/
void
! ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
! 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 *
--- 65,85 ----
* RowExclusiveLock mode upon return from this function.
*/
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 *
***************
*** 98,157 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
* 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. The user may have asked to check for
! * conflicts within this leaf partition and do "nothing" instead of
! * throwing an error. Be prepared in that case by initializing the
! * index information needed by ExecInsert() to perform speculative
! * insertions.
! */
! if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
! leaf_part_rri->ri_IndexRelationDescs == NULL)
! ExecOpenIndices(leaf_part_rri,
! mtstate != NULL &&
! mtstate->mt_onconflict != ONCONFLICT_NONE);
! estate->es_leaf_result_relations =
! lappend(estate->es_leaf_result_relations, leaf_part_rri);
! (*partitions)[i] = leaf_part_rri++;
! i++;
! }
}
/*
--- 92,152 ----
* processing.
*/
*partition_tuple_slot = MakeTupleTableSlot();
+ }
! /*
! * ExecInitPartition -- Prepare ResultRelInfo and tuple conversion map for
! * the partition with OID 'partOid'
! */
! void
! ExecInitPartition(ModifyTableState *mtstate,
! 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. The user may have asked to check for conflicts
! * within this leaf partition and do "nothing" instead of throwing an
! * error. Be prepared in that case by initializing the index information
! * needed by ExecInsert() to perform speculative insertions.
! */
! if (partRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
! partRelInfo->ri_IndexRelationDescs == NULL)
! ExecOpenIndices(partRelInfo,
! mtstate != NULL &&
! mtstate->mt_onconflict != ONCONFLICT_NONE);
! /* Store ResultRelInfo in *estate. */
! estate->es_leaf_result_relations =
! lappend(estate->es_leaf_result_relations, partRelInfo);
! /*
! * Save conversion map to convert a tuple routed to the partition from
! * the parent's type to the partition's.
! */
! *partTupConvMap = convert_tuples_by_name(RelationGetDescr(rootrel),
! RelationGetDescr(partrel),
! gettext_noop("could not convert row type"));
}
/*
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 306,316 **** ExecInsert(ModifyTableState *mtstate,
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions[leaf_part_index];
! /* We do not yet have a way to insert into a foreign partition */
! if (resultRelInfo->ri_FdwRoutine)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
--- 306,322 ----
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions[leaf_part_index];
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* The partition should be foreign */
! Assert(resultRelInfo->ri_FdwRoutine);
!
! /* We cannot insert into this foreign partition */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to foreign table \"%s\"",
! RelationGetRelationName(resultRelInfo->ri_RelationDesc))));
! }
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
***************
*** 1947,1963 **** 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(mtstate,
! rel,
! node->nominalRelation,
! estate,
&partition_dispatch_info,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
--- 1953,1969 ----
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,
***************
*** 1968,1973 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1974,2018 ----
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(mtstate,
+ 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++;
+ }
}
/*
***************
*** 2393,2398 **** ExecEndModifyTable(ModifyTableState *node)
--- 2438,2448 ----
{
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"
***************
*** 6439,6444 **** make_modifytable(PlannerInfo *root,
--- 6440,6446 ----
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
Bitmapset *direct_modify_plans;
+ List *partition_rels;
ListCell *lc;
int i;
***************
*** 6562,6567 **** make_modifytable(PlannerInfo *root,
--- 6564,6698 ----
node->fdwPrivLists = fdw_private_list;
node->fdwDirectModifyPlans = direct_modify_plans;
+ /*
+ * Also, if this is an INSERT into a partitioned table, build a list of
+ * RT indexes of partitions, and for each foreign partition, allow the FDW
+ * to construct private plan data and accumulate it all into another list.
+ *
+ * Note: ExecSetupPartitionTupleRouting() will expand partitions in the
+ * same order as these lists.
+ */
+ partition_rels = NIL;
+ fdw_private_list = NIL;
+ if (operation == CMD_INSERT &&
+ planner_rt_fetch(nominalRelation, root)->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *saved_withCheckOptionLists = node->withCheckOptionLists;
+ List *saved_returningLists = node->returningLists;
+ Plan *subplan = (Plan *) linitial(node->plans);
+ List *saved_tlist = subplan->targetlist;
+ Query **parent_parses;
+ Query *parent_parse;
+ Bitmapset *parent_relids;
+
+ /*
+ * Similarly to inheritance_planner(), we generate a modified query
+ * with each child as target by applying adjust_appendrel_attrs() to
+ * the query for its immediate parent, so build an array to store in
+ * the query for each parent.
+ */
+ parent_parses = (Query **)
+ palloc0((list_length(root->parse->rtable) + 1) * sizeof(Query *));
+
+ parent_parses[nominalRelation] = root->parse;
+ parent_relids = bms_make_singleton(nominalRelation);
+
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ Index parent_rti = appinfo->parent_relid;
+ Index child_rti = appinfo->child_relid;
+ RangeTblEntry *child_rte;
+ Query *child_parse;
+ FdwRoutine *fdwroutine = NULL;
+ List *fdw_private = NIL;
+
+ /* append_rel_list contains all append rels; ignore others */
+ if (!bms_is_member(parent_rti, parent_relids))
+ continue;
+
+ child_rte = planner_rt_fetch(child_rti, root);
+ Assert(child_rte->rtekind == RTE_RELATION);
+
+ /* No work if the child is a plain table */
+ if (child_rte->relkind == RELKIND_RELATION)
+ {
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list, NIL);
+ continue;
+ }
+
+ /*
+ * expand_inherited_rtentry() always processes a parent before any
+ * of that parent's children, so the query for its parent should
+ * already be available.
+ */
+ parent_parse = parent_parses[parent_rti];
+ Assert(parent_parse);
+
+ /* Generate the query with the child as target */
+ child_parse = (Query *)
+ adjust_appendrel_attrs(root,
+ (Node *) parent_parse,
+ 1, &appinfo);
+
+ /* Store the query if the child is a partitioned table */
+ if (child_rte->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ parent_parses[child_rti] = child_parse;
+ parent_relids = bms_add_member(parent_relids, child_rti);
+ continue;
+ }
+
+ /* The child should be a foreign table */
+ Assert(child_rte->relkind == RELKIND_FOREIGN_TABLE);
+
+ fdwroutine = GetFdwRoutineByRelId(child_rte->relid);
+ Assert(fdwroutine != NULL);
+
+ if (fdwroutine->PlanForeignModify != NULL)
+ {
+ List *tlist;
+
+ /* Replace the root query with the child query. */
+ root->parse = child_parse;
+
+ /* Adjust the plan node to refer to the child as target. */
+ node->nominalRelation = child_rti;
+ node->resultRelations = list_make1_int(child_rti);
+ node->withCheckOptionLists =
+ list_make1(child_parse->withCheckOptions);
+ node->returningLists =
+ list_make1(child_parse->returningList);
+
+ /*
+ * The column list of the child might have a different column
+ * order and/or a different set of dropped columns than that
+ * of its parent, so adjust the subplan's tlist as well.
+ */
+ tlist = preprocess_targetlist(root);
+ subplan->targetlist = tlist;
+
+ fdw_private = fdwroutine->PlanForeignModify(root,
+ node,
+ child_rti,
+ 0);
+
+ subplan->targetlist = saved_tlist;
+ node->nominalRelation = nominalRelation;
+ node->resultRelations = list_make1_int(nominalRelation);
+ node->withCheckOptionLists = saved_withCheckOptionLists;
+ node->returningLists = saved_returningLists;
+ root->parse = parent_parses[nominalRelation];
+ }
+
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list, fdw_private);
+ }
+ }
+ node->partition_rels = partition_rels;
+ node->fdwPartitionPrivLists = fdw_private_list;
+
return node;
}
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 879,888 **** subquery_planner(PlannerGlobal *glob, Query *parse,
reduce_outer_joins(root);
/*
! * Do the main planning. If we have an inherited target relation, that
! * needs special processing, else go straight to grouping_planner.
*/
! if (parse->resultRelation &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
--- 879,890 ----
reduce_outer_joins(root);
/*
! * Do the main planning. If we have an inherited UPDATE/DELETE target
! * relation, that needs special processing, else go straight to
! * grouping_planner.
*/
! if ((parse->commandType == CMD_UPDATE ||
! parse->commandType == CMD_DELETE) &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 858,863 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 858,867 ----
{
lfirst_int(l) += rtoffset;
}
+ foreach(l, splan->partition_rels)
+ {
+ lfirst_int(l) += rtoffset;
+ }
foreach(l, splan->resultRelations)
{
lfirst_int(l) += rtoffset;
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1975,1982 **** 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);
--- 1975,1983 ----
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);
***************
*** 2326,2332 **** 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
--- 2327,2333 ----
}
/*
! * 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
***************
*** 2338,2345 **** 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)
--- 2339,2344 ----
*** 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
***************
*** 2899,2911 **** 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
--- 2899,2904 ----
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 49,63 **** typedef struct PartitionDispatchData
typedef struct PartitionDispatchData *PartitionDispatch;
! extern void ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
! 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,
--- 49,68 ----
typedef struct PartitionDispatchData *PartitionDispatch;
! extern void ExecSetupPartitionTupleRouting(Relation rel,
PartitionDispatch **pd,
+ List **leaf_parts,
ResultRelInfo ***partitions,
TupleConversionMap ***tup_conv_maps,
TupleTableSlot **partition_tuple_slot,
int *num_parted, int *num_partitions);
+ extern void ExecInitPartition(ModifyTableState *mtstate,
+ EState *estate,
+ ResultRelInfo *rootRelInfo,
+ Oid partOid,
+ Index partRTindex,
+ ResultRelInfo *partRelInfo,
+ TupleConversionMap **partTupConvMap);
extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
PartitionDispatch *pd,
TupleTableSlot *slot,
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 181,187 **** extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! Relation partition_root,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecCleanUpTriggerState(EState *estate);
--- 181,187 ----
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! ResultRelInfo *partition_root,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecCleanUpTriggerState(EState *estate);
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 405,418 **** 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;
/* ----------------
--- 405,421 ----
/* 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;
/* ----------------
Fujita-san,
On 2017/12/12 21:21, Etsuro Fujita wrote:
Hi Maksim,
(2017/12/12 17:57), Maksim Milyutin wrote:
Your patch already is not applied on master. Please rebase it.
Done. Please find attached an updated version of the patch.
Thanks for sending the updated version of the patch.
I noticed that this patch introduces a partition_rels field in
ModifyTable, which contains the RT indexes of *all* leaf partitions in the
partition tree. That means we now expand the partition inheritance tree
in the planner even in the INSERT case, simply because some of the leaf
partitions might be foreign tables which must be looked at by the planner.
I'm somewhat concerned about the performance implications of that. Now,
to insert even a single row into a partitioned table, which may not even
be routed to any of its foreign partitions, we are going to have to expand
the inheritance twice -- once by the planner to handle foreign partitions
and then by the executor when setting up the tuple routing information.
Is there any reason why we have to to things this way, beside the fact
that the PlanForeignModify() API appears to be callable from only within a
valid planner context?
Thanks,
Amit
(2017/12/18 18:14), Amit Langote wrote:
I noticed that this patch introduces a partition_rels field in
ModifyTable, which contains the RT indexes of *all* leaf partitions in the
partition tree. That means we now expand the partition inheritance tree
in the planner even in the INSERT case, simply because some of the leaf
partitions might be foreign tables which must be looked at by the planner.
Yeah, the patch expands the inheritance tree at planning time as well to
build an RTE for each partition so that the FDW can use that RTE eg,
when called from PlanForeignModify.
I'm somewhat concerned about the performance implications of that. Now,
to insert even a single row into a partitioned table, which may not even
be routed to any of its foreign partitions, we are going to have to expand
the inheritance twice -- once by the planner to handle foreign partitions
and then by the executor when setting up the tuple routing information.Is there any reason why we have to to things this way, beside the fact
that the PlanForeignModify() API appears to be callable from only within a
valid planner context?
Another reason for that is for set_plan_references to get such RTEs to
record plan dependencies so that plancache.c invalidates cached plans
for foreign partitions.
I suspect that we could avoid the planning-time expansion by doing some
optimization when inserting a single row into a partitioned table.
Best regards,
Etsuro Fujita
InitResultRelInfo becomes unintelligible after this patch -- it was
straightforward but adding partition_root makes things shaky. Please
add a proper comment indicating what each argument is. (I wonder why
this function needs a local variable "partition_check" -- seems
pointless).
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 2017/12/18 23:25, Alvaro Herrera wrote:
(I wonder why
this function needs a local variable "partition_check" -- seems
pointless).
Before 15ce775faa4 [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=15ce775faa4, there were more than one line where
partition_check was being set, but maybe it still didn't have to be a
separate variable.
Thanks,
Amit
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=15ce775faa4
(2017/12/18 23:25), Alvaro Herrera wrote:
InitResultRelInfo becomes unintelligible after this patch -- it was
straightforward but adding partition_root makes things shaky. Please
add a proper comment indicating what each argument is.
I was thiking that the comment I added to the definition of the
ResultRelInfo struct in execnodes.h would make that function
intelligible, but I agree on that point. Please fined attached a new
version of the patch adding such comments.
Best regards,
Etsuro Fujita
Attachments:
tuple-routing-to-foreign-partitions-v7.patchtext/x-diff; name=tuple-routing-to-foreign-partitions-v7.patchDownload
*** 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
***************
*** 7088,7093 **** NOTICE: drop cascades to foreign table bar2
--- 7088,7295 ----
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
***************
*** 1680,1685 **** drop table loct1;
--- 1680,1748 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo');
+
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ insert into pt values (1, 1), (2, 1);
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ insert into pt values (1, 2), (2, 2) returning *;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ execute q1;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+
+ -- Check INSERT into a multi-level partitioned table
+ create table mlpt (a int, b int, c varchar) partition by range (a);
+ create table mlptp1 partition of mlpt for values from (100) to (200) partition by range(b);
+ create table mlptp2 partition of mlpt for values from (200) to (300);
+ create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 100 and b < 200), c varchar);
+ create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 200 and b < 300), c varchar);
+ create foreign table mlptp1p1 partition of mlptp1 for values from (100) to (200) server loopback options (table_name 'locfoo');
+ create foreign table mlptp1p2 partition of mlptp1 for values from (200) to (300) server loopback options (table_name 'locbar');
+
+ explain (verbose, costs off)
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+
+ select tableoid::regclass, * FROM mlpt;
+ select tableoid::regclass, * FROM mlptp1;
+ select tableoid::regclass, * FROM mlptp2;
+ select tableoid::regclass, * FROM mlptp1p1;
+ select tableoid::regclass, * FROM mlptp1p2;
+
+ drop table mlpt;
+ drop table locfoo;
+ drop table locbar;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2998,3008 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<sect3 id="ddl-partitioning-declarative-example">
--- 2998,3006 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<sect3 id="ddl-partitioning-declarative-example">
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2473,2489 **** 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(NULL,
! cstate->rel,
! 1,
! estate,
&partition_dispatch_info,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
--- 2473,2491 ----
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,
***************
*** 2495,2500 **** CopyFrom(CopyState cstate)
--- 2497,2537 ----
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(NULL,
+ 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
***************
*** 2643,2653 **** 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
--- 2680,2695 ----
saved_resultRelInfo = resultRelInfo;
resultRelInfo = cstate->partitions[leaf_part_index];
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* The partition should be foreign */
! Assert(resultRelInfo->ri_FdwRoutine);
!
! /* We do not yet have a way to copy into a foreign partition */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route copied tuples to a foreign table")));
! }
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 117,122 **** static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
--- 117,126 ----
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
ExplainState *es);
+ static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ const char *operation, bool labeltarget,
+ bool main_target, int subplan_index, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
***************
*** 829,834 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 833,849 ----
if (((ModifyTable *) plan)->exclRelRTI)
*rels_used = bms_add_member(*rels_used,
((ModifyTable *) plan)->exclRelRTI);
+ if (((ModifyTable *) plan)->partition_rels)
+ {
+ ListCell *lc;
+
+ foreach(lc, ((ModifyTable *) plan)->partition_rels)
+ {
+ Index rti = lfirst_int(lc);
+
+ *rels_used = bms_add_member(*rels_used, rti);
+ }
+ }
break;
default:
break;
***************
*** 2917,2973 **** 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 */
--- 2932,2958 ----
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 */
***************
*** 3024,3029 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
--- 3009,3086 ----
}
/*
+ * Show an actual target relation
+ */
+ static void
+ show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ const char *operation, bool labeltarget,
+ bool main_target, int subplan_index, ExplainState *es)
+ {
+ if (labeltarget)
+ {
+ /* Open a group for this target */
+ if (main_target)
+ ExplainOpenGroup("Target Table", NULL, true, es);
+ else
+ ExplainOpenGroup("Partition Table", NULL, true, es);
+
+ /*
+ * In text mode, decorate each target with operation type, so that
+ * ExplainTargetRel's output of " on foo" will read nicely.
+ */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, operation);
+ }
+
+ /* Identify target */
+ ExplainTargetRel((Plan *) node,
+ resultRelInfo->ri_RangeTableIndex,
+ es);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoChar(es->str, '\n');
+ es->indent++;
+ }
+ }
+
+ /* Give FDW a chance if needed */
+ if (fdwroutine != NULL &&
+ fdwroutine->ExplainForeignModify != NULL &&
+ !resultRelInfo->ri_usesFdwDirectModify)
+ {
+ List *fdw_private;
+
+ if (main_target)
+ fdw_private = (List *) list_nth(node->fdwPrivLists, subplan_index);
+ else
+ fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, subplan_index);
+
+ fdwroutine->ExplainForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ main_target ? subplan_index : 0,
+ es);
+ }
+
+ if (labeltarget)
+ {
+ /* Undo the indentation we added in text format */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+
+ /* Close the group */
+ if (main_target)
+ ExplainCloseGroup("Target Table", NULL, true, es);
+ else
+ ExplainCloseGroup("Partition Table", NULL, true, es);
+ }
+ }
+
+ /*
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
* BitmapAnd, or BitmapOr node.
*
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1100,1111 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
--- 1100,1114 ----
Relation resultRel = resultRelInfo->ri_RelationDesc;
TriggerDesc *trigDesc = resultRel->trigdesc;
FdwRoutine *fdwroutine;
+ bool is_valid;
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
CheckCmdReplicaIdentity(resultRel, operation);
+ if (resultRelInfo->ri_PartitionRoot && operation == CMD_INSERT)
+ resultRelInfo->ri_PartitionIsValid = true;
break;
case RELKIND_SEQUENCE:
ereport(ERROR,
***************
*** 1172,1195 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
switch (operation)
{
case CMD_INSERT:
!
! /*
! * If foreign partition to do tuple-routing for, skip the
! * check; it's disallowed elsewhere.
! */
! if (resultRelInfo->ri_PartitionRoot)
! break;
if (fdwroutine->ExecForeignInsert == NULL)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot insert into foreign table \"%s\"",
! RelationGetRelationName(resultRel))));
if (fdwroutine->IsForeignRelUpdatable != NULL &&
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("foreign table \"%s\" does not allow inserts",
! RelationGetRelationName(resultRel))));
break;
case CMD_UPDATE:
if (fdwroutine->ExecForeignUpdate == NULL)
--- 1175,1202 ----
switch (operation)
{
case CMD_INSERT:
! is_valid = true;
if (fdwroutine->ExecForeignInsert == NULL)
! {
! if (!resultRelInfo->ri_PartitionRoot)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot insert into foreign table \"%s\"",
! RelationGetRelationName(resultRel))));
! is_valid = false;
! }
if (fdwroutine->IsForeignRelUpdatable != NULL &&
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
! {
! if (!resultRelInfo->ri_PartitionRoot)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("foreign table \"%s\" does not allow inserts",
! RelationGetRelationName(resultRel))));
! is_valid = false;
! }
! if (resultRelInfo->ri_PartitionRoot)
! resultRelInfo->ri_PartitionIsValid = is_valid;
break;
case CMD_UPDATE:
if (fdwroutine->ExecForeignUpdate == NULL)
***************
*** 1298,1303 **** CheckValidRowMarkRel(Relation rel, RowMarkType markType)
--- 1305,1316 ----
/*
* Initialize ResultRelInfo data for one result relation
*
+ * resultRelInfo - ResultRelInfo for result relation to initialize
+ * resultRelationDesc - relation descriptor for result relation
+ * resultRelationIndex - RT index of result relation
+ * partition_root - ResultRelInfo for root partitioned table (if any)
+ * instrument_options - OR of InstrumentOption flags
+ *
* Caution: before Postgres 9.1, this function included the relkind checking
* that's now in CheckValidResultRel, and it also did ExecOpenIndices if
* appropriate. Be sure callers cover those needs.
***************
*** 1306,1312 **** void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! Relation partition_root,
int instrument_options)
{
List *partition_check = NIL;
--- 1319,1325 ----
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! ResultRelInfo *partition_root,
int instrument_options)
{
List *partition_check = NIL;
***************
*** 1362,1369 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
*/
partition_check = RelationGetPartitionQual(resultRelationDesc);
- resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
}
/*
--- 1375,1383 ----
*/
partition_check = RelationGetPartitionQual(resultRelationDesc);
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionCheck = partition_check;
+ resultRelInfo->ri_PartitionIsValid = false;
}
/*
***************
*** 1890,1904 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
{
char *val_desc;
Relation orig_rel = rel;
/* See the comment above. */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 1904,1919 ----
{
char *val_desc;
Relation orig_rel = rel;
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/* See the comment above. */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 1909,1918 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 1924,1938 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 1964,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1984,1990 ----
char *val_desc;
Relation orig_rel = rel;
TupleDesc orig_tupdesc = RelationGetDescr(rel);
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/*
* If the tuple has been routed, it's been converted to the
***************
*** 1972,1983 **** ExecConstraints(ResultRelInfo *resultRelInfo,
* rowtype so that val_desc shown error message matches the
* input tuple.
*/
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(orig_tupdesc, tupdesc,
--- 1993,2004 ----
* rowtype so that val_desc shown error message matches the
* input tuple.
*/
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(orig_tupdesc, tupdesc,
***************
*** 1988,1997 **** ExecConstraints(ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2009,2023 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 2017,2031 **** ExecConstraints(ResultRelInfo *resultRelInfo,
{
char *val_desc;
Relation orig_rel = rel;
/* See the comment above. */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 2043,2058 ----
{
char *val_desc;
Relation orig_rel = rel;
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
/* See the comment above. */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2036,2045 **** ExecConstraints(ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2063,2077 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
***************
*** 2111,2116 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
--- 2143,2149 ----
*/
if (!ExecQual(wcoExpr, econtext))
{
+ ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
char *val_desc;
Bitmapset *modifiedCols;
Bitmapset *insertedCols;
***************
*** 2129,2141 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
*/
case WCO_VIEW_CHECK:
/* See the comment in ExecConstraints(). */
! if (resultRelInfo->ri_PartitionRoot)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = resultRelInfo->ri_PartitionRoot;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 2162,2174 ----
*/
case WCO_VIEW_CHECK:
/* See the comment in ExecConstraints(). */
! if (rootRelInfo)
{
HeapTuple tuple = ExecFetchSlotTuple(slot);
TupleDesc old_tupdesc = RelationGetDescr(rel);
TupleConversionMap *map;
! rel = rootRelInfo->ri_RelationDesc;
tupdesc = RelationGetDescr(rel);
/* a reverse map */
map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2146,2155 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
- }
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
--- 2179,2193 ----
ExecSetSlotDescriptor(slot, tupdesc);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
}
! insertedCols = GetInsertedColumns(rootRelInfo, estate);
! updatedCols = GetUpdatedColumns(rootRelInfo, estate);
! }
! else
! {
! insertedCols = GetInsertedColumns(resultRelInfo, estate);
! updatedCols = GetUpdatedColumns(resultRelInfo, estate);
! }
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 44,49 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
--- 44,51 ----
* Output arguments:
* 'pd' receives an array of PartitionDispatch objects with one entry for
* every partitioned table in the partition tree
+ * 'leaf_parts' receives a list of relation OIDs with one entry for every leaf
+ * partition in the partition tree
* 'partitions' receives an array of ResultRelInfo* objects with one entry for
* every leaf partition in the partition tree
* 'tup_conv_maps' receives an array of TupleConversionMap objects with one
***************
*** 63,91 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
* RowExclusiveLock mode upon return from this function.
*/
void
! ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
! 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 *
--- 65,85 ----
* RowExclusiveLock mode upon return from this function.
*/
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 *
***************
*** 98,157 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
* 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. The user may have asked to check for
! * conflicts within this leaf partition and do "nothing" instead of
! * throwing an error. Be prepared in that case by initializing the
! * index information needed by ExecInsert() to perform speculative
! * insertions.
! */
! if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
! leaf_part_rri->ri_IndexRelationDescs == NULL)
! ExecOpenIndices(leaf_part_rri,
! mtstate != NULL &&
! mtstate->mt_onconflict != ONCONFLICT_NONE);
! estate->es_leaf_result_relations =
! lappend(estate->es_leaf_result_relations, leaf_part_rri);
! (*partitions)[i] = leaf_part_rri++;
! i++;
! }
}
/*
--- 92,152 ----
* processing.
*/
*partition_tuple_slot = MakeTupleTableSlot();
+ }
! /*
! * ExecInitPartition -- Prepare ResultRelInfo and tuple conversion map for
! * the partition with OID 'partOid'
! */
! void
! ExecInitPartition(ModifyTableState *mtstate,
! 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. The user may have asked to check for conflicts
! * within this leaf partition and do "nothing" instead of throwing an
! * error. Be prepared in that case by initializing the index information
! * needed by ExecInsert() to perform speculative insertions.
! */
! if (partRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
! partRelInfo->ri_IndexRelationDescs == NULL)
! ExecOpenIndices(partRelInfo,
! mtstate != NULL &&
! mtstate->mt_onconflict != ONCONFLICT_NONE);
! /* Store ResultRelInfo in *estate. */
! estate->es_leaf_result_relations =
! lappend(estate->es_leaf_result_relations, partRelInfo);
! /*
! * Save conversion map to convert a tuple routed to the partition from
! * the parent's type to the partition's.
! */
! *partTupConvMap = convert_tuples_by_name(RelationGetDescr(rootrel),
! RelationGetDescr(partrel),
! gettext_noop("could not convert row type"));
}
/*
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 306,316 **** ExecInsert(ModifyTableState *mtstate,
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions[leaf_part_index];
! /* We do not yet have a way to insert into a foreign partition */
! if (resultRelInfo->ri_FdwRoutine)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
--- 306,322 ----
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions[leaf_part_index];
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* The partition should be foreign */
! Assert(resultRelInfo->ri_FdwRoutine);
!
! /* We cannot insert into this foreign partition */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to foreign table \"%s\"",
! RelationGetRelationName(resultRelInfo->ri_RelationDesc))));
! }
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
***************
*** 1947,1963 **** 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(mtstate,
! rel,
! node->nominalRelation,
! estate,
&partition_dispatch_info,
&partitions,
&partition_tupconv_maps,
&partition_tuple_slot,
--- 1953,1969 ----
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,
***************
*** 1968,1973 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1974,2018 ----
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(mtstate,
+ 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++;
+ }
}
/*
***************
*** 2393,2398 **** ExecEndModifyTable(ModifyTableState *node)
--- 2438,2448 ----
{
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"
***************
*** 6439,6444 **** make_modifytable(PlannerInfo *root,
--- 6440,6446 ----
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
Bitmapset *direct_modify_plans;
+ List *partition_rels;
ListCell *lc;
int i;
***************
*** 6562,6567 **** make_modifytable(PlannerInfo *root,
--- 6564,6698 ----
node->fdwPrivLists = fdw_private_list;
node->fdwDirectModifyPlans = direct_modify_plans;
+ /*
+ * Also, if this is an INSERT into a partitioned table, build a list of
+ * RT indexes of partitions, and for each foreign partition, allow the FDW
+ * to construct private plan data and accumulate it all into another list.
+ *
+ * Note: ExecSetupPartitionTupleRouting() will expand partitions in the
+ * same order as these lists.
+ */
+ partition_rels = NIL;
+ fdw_private_list = NIL;
+ if (operation == CMD_INSERT &&
+ planner_rt_fetch(nominalRelation, root)->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *saved_withCheckOptionLists = node->withCheckOptionLists;
+ List *saved_returningLists = node->returningLists;
+ Plan *subplan = (Plan *) linitial(node->plans);
+ List *saved_tlist = subplan->targetlist;
+ Query **parent_parses;
+ Query *parent_parse;
+ Bitmapset *parent_relids;
+
+ /*
+ * Similarly to inheritance_planner(), we generate a modified query
+ * with each child as target by applying adjust_appendrel_attrs() to
+ * the query for its immediate parent, so build an array to store in
+ * the query for each parent.
+ */
+ parent_parses = (Query **)
+ palloc0((list_length(root->parse->rtable) + 1) * sizeof(Query *));
+
+ parent_parses[nominalRelation] = root->parse;
+ parent_relids = bms_make_singleton(nominalRelation);
+
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ Index parent_rti = appinfo->parent_relid;
+ Index child_rti = appinfo->child_relid;
+ RangeTblEntry *child_rte;
+ Query *child_parse;
+ FdwRoutine *fdwroutine = NULL;
+ List *fdw_private = NIL;
+
+ /* append_rel_list contains all append rels; ignore others */
+ if (!bms_is_member(parent_rti, parent_relids))
+ continue;
+
+ child_rte = planner_rt_fetch(child_rti, root);
+ Assert(child_rte->rtekind == RTE_RELATION);
+
+ /* No work if the child is a plain table */
+ if (child_rte->relkind == RELKIND_RELATION)
+ {
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list, NIL);
+ continue;
+ }
+
+ /*
+ * expand_inherited_rtentry() always processes a parent before any
+ * of that parent's children, so the query for its parent should
+ * already be available.
+ */
+ parent_parse = parent_parses[parent_rti];
+ Assert(parent_parse);
+
+ /* Generate the query with the child as target */
+ child_parse = (Query *)
+ adjust_appendrel_attrs(root,
+ (Node *) parent_parse,
+ 1, &appinfo);
+
+ /* Store the query if the child is a partitioned table */
+ if (child_rte->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ parent_parses[child_rti] = child_parse;
+ parent_relids = bms_add_member(parent_relids, child_rti);
+ continue;
+ }
+
+ /* The child should be a foreign table */
+ Assert(child_rte->relkind == RELKIND_FOREIGN_TABLE);
+
+ fdwroutine = GetFdwRoutineByRelId(child_rte->relid);
+ Assert(fdwroutine != NULL);
+
+ if (fdwroutine->PlanForeignModify != NULL)
+ {
+ List *tlist;
+
+ /* Replace the root query with the child query. */
+ root->parse = child_parse;
+
+ /* Adjust the plan node to refer to the child as target. */
+ node->nominalRelation = child_rti;
+ node->resultRelations = list_make1_int(child_rti);
+ node->withCheckOptionLists =
+ list_make1(child_parse->withCheckOptions);
+ node->returningLists =
+ list_make1(child_parse->returningList);
+
+ /*
+ * The column list of the child might have a different column
+ * order and/or a different set of dropped columns than that
+ * of its parent, so adjust the subplan's tlist as well.
+ */
+ tlist = preprocess_targetlist(root);
+ subplan->targetlist = tlist;
+
+ fdw_private = fdwroutine->PlanForeignModify(root,
+ node,
+ child_rti,
+ 0);
+
+ subplan->targetlist = saved_tlist;
+ node->nominalRelation = nominalRelation;
+ node->resultRelations = list_make1_int(nominalRelation);
+ node->withCheckOptionLists = saved_withCheckOptionLists;
+ node->returningLists = saved_returningLists;
+ root->parse = parent_parses[nominalRelation];
+ }
+
+ partition_rels = lappend_int(partition_rels, child_rti);
+ fdw_private_list = lappend(fdw_private_list, fdw_private);
+ }
+ }
+ node->partition_rels = partition_rels;
+ node->fdwPartitionPrivLists = fdw_private_list;
+
return node;
}
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 879,888 **** subquery_planner(PlannerGlobal *glob, Query *parse,
reduce_outer_joins(root);
/*
! * Do the main planning. If we have an inherited target relation, that
! * needs special processing, else go straight to grouping_planner.
*/
! if (parse->resultRelation &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
--- 879,890 ----
reduce_outer_joins(root);
/*
! * Do the main planning. If we have an inherited UPDATE/DELETE target
! * relation, that needs special processing, else go straight to
! * grouping_planner.
*/
! if ((parse->commandType == CMD_UPDATE ||
! parse->commandType == CMD_DELETE) &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 858,863 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 858,867 ----
{
lfirst_int(l) += rtoffset;
}
+ foreach(l, splan->partition_rels)
+ {
+ lfirst_int(l) += rtoffset;
+ }
foreach(l, splan->resultRelations)
{
lfirst_int(l) += rtoffset;
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1975,1982 **** 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);
--- 1975,1983 ----
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);
***************
*** 2326,2332 **** 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
--- 2327,2333 ----
}
/*
! * 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
***************
*** 2338,2345 **** 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)
--- 2339,2344 ----
*** 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
***************
*** 2899,2911 **** 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
--- 2899,2904 ----
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 49,63 **** typedef struct PartitionDispatchData
typedef struct PartitionDispatchData *PartitionDispatch;
! extern void ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
! 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,
--- 49,68 ----
typedef struct PartitionDispatchData *PartitionDispatch;
! extern void ExecSetupPartitionTupleRouting(Relation rel,
PartitionDispatch **pd,
+ List **leaf_parts,
ResultRelInfo ***partitions,
TupleConversionMap ***tup_conv_maps,
TupleTableSlot **partition_tuple_slot,
int *num_parted, int *num_partitions);
+ extern void ExecInitPartition(ModifyTableState *mtstate,
+ EState *estate,
+ ResultRelInfo *rootRelInfo,
+ Oid partOid,
+ Index partRTindex,
+ ResultRelInfo *partRelInfo,
+ TupleConversionMap **partTupConvMap);
extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
PartitionDispatch *pd,
TupleTableSlot *slot,
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 181,187 **** extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! Relation partition_root,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecCleanUpTriggerState(EState *estate);
--- 181,187 ----
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
! ResultRelInfo *partition_root,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern void ExecCleanUpTriggerState(EState *estate);
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 405,418 **** 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;
/* ----------------
--- 405,421 ----
/* 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;
/* ----------------
Etsuro,
* Etsuro Fujita (fujita.etsuro@lab.ntt.co.jp) wrote:
(2017/12/18 23:25), Alvaro Herrera wrote:
InitResultRelInfo becomes unintelligible after this patch -- it was
straightforward but adding partition_root makes things shaky. Please
add a proper comment indicating what each argument is.I was thiking that the comment I added to the definition of the
ResultRelInfo struct in execnodes.h would make that function intelligible,
but I agree on that point. Please fined attached a new version of the patch
adding such comments.
I'm afraid a good bit of this patch is now failing to apply. I don't
have much else to say except to echo the performance concern that Amit
Langote raised about expanding the inheritence tree twice.
Thanks!
Stephen
(2018/01/25 23:33), Stephen Frost wrote:
I'm afraid a good bit of this patch is now failing to apply. I don't
have much else to say except to echo the performance concern that Amit
Langote raised about expanding the inheritence tree twice.
To address that concern, I'm thinking to redesign the patch so that it
wouldn't expand the tree at planning time anymore. I don't have any
clear solution for that yet, but what I have in mind now is to add new
FDW APIs to the executor, instead, so that the FDW could 1) create stuff
such as a query for remote INSERT as PlanForeignModify and 2)
initialize/end the remote INSERT operation as BeginForeignModify and
EndForeignModify, somewhere in the executor. (For #1, I'm thinking to
add an API for that to ExecSetupPartitionTupleRouting or
ExecInitPartitionResultRelInfo proposed by the patch by Amit Langote
[1]: https://commitfest.postgresql.org/16/1415/
mark this as Returned with feedback.
Thanks for the comment!
Best regards,
Etsuro Fujita
(2018/02/02 19:33), Etsuro Fujita wrote:
(2018/01/25 23:33), Stephen Frost wrote:
I'm afraid a good bit of this patch is now failing to apply. I don't
have much else to say except to echo the performance concern that Amit
Langote raised about expanding the inheritence tree twice.To address that concern, I'm thinking to redesign the patch so that it
wouldn't expand the tree at planning time anymore. I don't have any
clear solution for that yet, but what I have in mind now is to add new
FDW APIs to the executor, instead, so that the FDW could 1) create stuff
such as a query for remote INSERT as PlanForeignModify and 2)
initialize/end the remote INSERT operation as BeginForeignModify and
EndForeignModify, somewhere in the executor. (For #1, I'm thinking to
add an API for that to ExecSetupPartitionTupleRouting or
ExecInitPartitionResultRelInfo proposed by the patch by Amit Langote
[1].) Anyway, I'll work on this after reviewing that patch, so I'll mark
this as Returned with feedback.
CORRECTION: I'm planning to submit a new version to the March CF, so I
set the status to "Moved to next CF".
Best regards,
Etsuro Fujita
(2018/02/02 19:33), Etsuro Fujita wrote:
(2018/01/25 23:33), Stephen Frost wrote:
I'm afraid a good bit of this patch is now failing to apply. I don't
have much else to say except to echo the performance concern that Amit
Langote raised about expanding the inheritence tree twice.To address that concern, I'm thinking to redesign the patch so that it
wouldn't expand the tree at planning time anymore. I don't have any
clear solution for that yet, but what I have in mind now is to add new
FDW APIs to the executor, instead, so that the FDW could 1) create stuff
such as a query for remote INSERT as PlanForeignModify and 2)
initialize/end the remote INSERT operation as BeginForeignModify and
EndForeignModify, somewhere in the executor.
New FDW APIs I would like to propose for that are:
void
BeginForeignRouting(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo,
int partition_index);
Prepare for a tuple-routing operation on a foreign table. This is
called from ExecSetupPartitionTupleRouting and ExecInitPartitionInfo.
TupleTableSlot *
ExecForeignRouting(EState *estate,
ResultRelInfo *resultRelInfo,
TupleTableSlot *slot);
Route one tuple to the foreign table. This is called from ExecInsert.
void
EndForeignRouting(EState *estate,
ResultRelInfo *resultRelInfo);
End the operation and release resources. This is called from
ExecCleanupTupleRouting.
Attached are WIP patches for that:
Patch postgres-fdw-refactoring-WIP.patch: refactoring patch for
postgres_fdw.c to reduce duplicate code.
Patch foreign-routing-fdwapi-WIP.patch: main patch to add new FDW APIs,
which is created on top of patch postgres-fdw-refactoring-WIP.patch and
the lazy-initialization-of-partition-info patch [1]/messages/by-id/5A8BFB31.6030707@lab.ntt.co.jp.
By this change we don't need to expand the inheritance tree at planning
time, so no need to worry about the performance concern. Maybe I'm
missing something, though. Early feedback would be greatly appreciated.
Best regards,
Etsuro Fujita
Attachments:
foreign-routing-fdwapi-WIP.patchtext/x-diff; name=foreign-routing-fdwapi-WIP.patchDownload
*** 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;
***************
*** 342,351 **** SELECT tableoid::regclass, * FROM p2;
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
--- 342,351 ----
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to foreign table "p1"
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to foreign table "p1"
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7364,7369 **** NOTICE: drop cascades to foreign table bar2
--- 7364,7448 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ create table pt (a int, b int, c text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b int, c text, constraint locp1_pkey primary key (b));
+ create table loct2 (b int, c text, a int check (a in (2)), constraint locp2_pkey primary key (b));
+ create foreign table ptp1 partition of pt for values in (1) server loopback options (table_name 'loct1');
+ create foreign table ptp2 partition of pt for values in (2) server loopback options (table_name 'loct2');
+ insert into pt values (1, 1, 'foo');
+ insert into pt values (1, 2, 'bar') returning *;
+ a | b | c
+ ---+---+-----
+ 1 | 2 | bar
+ (1 row)
+
+ insert into pt values (2, 1, 'baz') returning *;
+ a | b | c
+ ---+---+-----
+ 2 | 1 | baz
+ (1 row)
+
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ ptp1 | 1 | 1 | foo
+ ptp1 | 1 | 2 | bar
+ ptp2 | 2 | 1 | baz
+ (3 rows)
+
+ select tableoid::regclass, * FROM ptp1;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ ptp1 | 1 | 1 | foo
+ ptp1 | 1 | 2 | bar
+ (2 rows)
+
+ select tableoid::regclass, * FROM ptp2;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ ptp2 | 2 | 1 | baz
+ (1 row)
+
+ insert into pt values (2, 1, 'baz');
+ ERROR: duplicate key value violates unique constraint "locp2_pkey"
+ DETAIL: Key (b)=(1) already exists.
+ CONTEXT: Remote SQL command: INSERT INTO public.loct2(a, b, c) VALUES ($1, $2, $3)
+ insert into pt values (2, 1, 'baz') on conflict do nothing;
+ insert into pt values (2, 2, 'qux') on conflict do nothing returning *;
+ a | b | c
+ ---+---+-----
+ 2 | 2 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM pt;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ ptp1 | 1 | 1 | foo
+ ptp1 | 1 | 2 | bar
+ ptp2 | 2 | 1 | baz
+ ptp2 | 2 | 2 | qux
+ (4 rows)
+
+ select tableoid::regclass, * FROM ptp1;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ ptp1 | 1 | 1 | foo
+ ptp1 | 1 | 2 | bar
+ (2 rows)
+
+ select tableoid::regclass, * FROM ptp2;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ ptp2 | 2 | 1 | baz
+ ptp2 | 2 | 2 | qux
+ (2 rows)
+
+ drop table pt;
+ drop table loct1;
+ drop table loct2;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 319,324 **** static TupleTableSlot *postgresExecForeignDelete(EState *estate,
--- 319,332 ----
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
+ static void postgresBeginForeignRouting(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ int partition_index);
+ static TupleTableSlot *postgresExecForeignRouting(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot);
+ static void postgresEndForeignRouting(EState *estate,
+ ResultRelInfo *resultRelInfo);
static int postgresIsForeignRelUpdatable(Relation rel);
static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
***************
*** 473,478 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 481,489 ----
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
+ routine->BeginForeignRouting = postgresBeginForeignRouting;
+ routine->ExecForeignRouting = postgresExecForeignRouting;
+ routine->EndForeignRouting = postgresEndForeignRouting;
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
routine->PlanDirectModify = postgresPlanDirectModify;
routine->BeginDirectModify = postgresBeginDirectModify;
***************
*** 1857,1862 **** postgresEndForeignModify(EState *estate,
--- 1868,1986 ----
}
/*
+ * postgresBeginForeignRouting
+ * Begin a row-routing operation on a foreign table
+ */
+ static void
+ postgresBeginForeignRouting(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ int partition_index)
+ {
+ PgFdwModifyState *fmstate;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int attnum;
+ RangeTblEntry *rte;
+ Query *query;
+ PlannerInfo *root;
+ StringInfoData sql;
+ List *targetAttrs = NIL;
+ List *retrieved_attrs = NIL;
+ bool doNothing = false;
+
+ initStringInfo(&sql);
+
+ /* Set up largely-dummy planner state */
+ rte = makeNode(RangeTblEntry);
+ rte->rtekind = RTE_RELATION;
+ rte->relid = RelationGetRelid(rel);
+ rte->relkind = RELKIND_FOREIGN_TABLE;
+ query = makeNode(Query);
+ query->commandType = CMD_INSERT;
+ query->resultRelation = 1;
+ query->rtable = list_make1(rte);
+ root = makeNode(PlannerInfo);
+ root->parse = query;
+
+ /* We transmit all columns that are defined in the foreign table. */
+ for (attnum = 1; attnum <= tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (!attr->attisdropped)
+ targetAttrs = lappend_int(targetAttrs, attnum);
+ }
+
+ /* We only support DO NOTHING without an inference specification. */
+ if (mtstate->mt_onconflict == ONCONFLICT_NOTHING)
+ doNothing = true;
+ else if (mtstate->mt_onconflict != ONCONFLICT_NONE)
+ elog(ERROR, "unexpected ON CONFLICT specification: %d",
+ (int) mtstate->mt_onconflict);
+
+ /* Construct the SQL command string. */
+ deparseInsertSql(&sql, root, 1, rel, targetAttrs, doNothing,
+ resultRelInfo->ri_returningList, &retrieved_attrs);
+
+ /* Construct an execution state. */
+ fmstate = create_fdw_modify_state(mtstate,
+ resultRelInfo,
+ CMD_INSERT,
+ 0, /* dummy subplan index */
+ sql.data,
+ targetAttrs,
+ retrieved_attrs != NIL,
+ retrieved_attrs);
+
+ resultRelInfo->ri_FdwState = fmstate;
+ }
+
+ /*
+ * postgresExecForeignRouting
+ * Route one row to a foreign table
+ */
+ static TupleTableSlot *
+ postgresExecForeignRouting(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+ const char **p_values;
+ int n_rows;
+
+ /* Set up the prepared statement on the remote server, if we didn't yet */
+ if (!fmstate->p_name)
+ prepare_foreign_modify(fmstate);
+
+ /* Convert parameters needed by prepared statement to text form */
+ p_values = convert_prep_stmt_params(fmstate, NULL, slot);
+
+ /* Execute the prepared statement and fetch RETURNING tuple if any */
+ n_rows = execute_prep_stmt(fmstate, p_values, slot);
+
+ MemoryContextReset(fmstate->temp_cxt);
+
+ /* Return NULL if nothing was inserted on the remote end */
+ return (n_rows > 0) ? slot : NULL;
+ }
+
+ /*
+ * postgresEndForeignRouting
+ * Finish a row-routing operation on a foreign table
+ */
+ static void
+ postgresEndForeignRouting(EState *estate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+
+ Assert(fmstate != NULL);
+
+ /* Destroy the execution state. */
+ finish_foreign_modify(fmstate);
+ }
+
+ /*
* postgresIsForeignRelUpdatable
* Determine whether a foreign table supports INSERT, UPDATE and/or
* DELETE.
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1759,1764 **** drop table loct1;
--- 1759,1794 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ create table pt (a int, b int, c text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b int, c text, constraint locp1_pkey primary key (b));
+ create table loct2 (b int, c text, a int check (a in (2)), constraint locp2_pkey primary key (b));
+ create foreign table ptp1 partition of pt for values in (1) server loopback options (table_name 'loct1');
+ create foreign table ptp2 partition of pt for values in (2) server loopback options (table_name 'loct2');
+
+ insert into pt values (1, 1, 'foo');
+ insert into pt values (1, 2, 'bar') returning *;
+ insert into pt values (2, 1, 'baz') returning *;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM ptp1;
+ select tableoid::regclass, * FROM ptp2;
+
+ insert into pt values (2, 1, 'baz');
+ insert into pt values (2, 1, 'baz') on conflict do nothing;
+ insert into pt values (2, 2, 'qux') on conflict do nothing returning *;
+
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM ptp1;
+ select tableoid::regclass, * FROM ptp2;
+
+ drop table pt;
+ drop table loct1;
+ drop table loct2;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2616,2626 **** CopyFrom(CopyState cstate)
Assert(resultRelInfo != NULL);
}
! /* 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
--- 2616,2626 ----
Assert(resultRelInfo != NULL);
}
! /* We do not yet have a way to copy into a foreign partition */
if (resultRelInfo->ri_FdwRoutine)
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
***************
*** 2814,2820 **** CopyFrom(CopyState cstate)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
--- 2814,2820 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(NULL, cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1172,1189 **** 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,
--- 1172,1193 ----
switch (operation)
{
case CMD_INSERT:
if (resultRelInfo->ri_PartitionRoot)
! {
! if (fdwroutine->ExecForeignRouting == NULL)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to foreign table \"%s\"",
! RelationGetRelationName(resultRel))));
! }
! else
! {
! 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,
***************
*** 1364,1369 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1368,1374 ----
resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionIsValid = false;
}
/*
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 17,22 ****
--- 17,23 ----
#include "catalog/pg_inherits_fn.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/lsyscache.h"
***************
*** 162,173 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
gettext_noop("could not convert row type"));
/*
! * Verify result relation is a valid target for an INSERT. An
! * UPDATE of a partition-key becomes a DELETE+INSERT operation,
! * so this check is required even when the operation is
! * CMD_UPDATE.
*/
! CheckValidResultRel(leaf_part_rri, CMD_INSERT);
}
}
--- 163,175 ----
gettext_noop("could not convert row type"));
/*
! * Also let the FDW init itself if this parition is foreign.
*/
! if (leaf_part_rri->ri_FdwRoutine != NULL &&
! leaf_part_rri->ri_FdwRoutine->BeginForeignRouting != NULL)
! leaf_part_rri->ri_FdwRoutine->BeginForeignRouting(mtstate,
! leaf_part_rri,
! i);
}
}
***************
*** 342,354 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
estate->es_instrument);
/*
- * Verify result relation is a valid target for an INSERT. An UPDATE
- * of a partition-key becomes a DELETE+INSERT operation, so this check
- * is still required when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
-
- /*
* Since we've just initialized this ResultRelInfo, it's not in
* any list attached to the estate as yet. Add it, so that it can
* be found later.
--- 344,349 ----
***************
*** 468,473 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 463,469 ----
returningList = map_partition_varattnos(returningList, firstVarno,
partrel, firstResultRel,
NULL);
+ leaf_part_rri->ri_returningList = returningList;
/*
* Initialize the projection itself.
***************
*** 498,503 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 494,509 ----
MemoryContextSwitchTo(oldContext);
+ /*
+ * Also let the FDW init itself if this parition is foreign.
+ */
+ if (mtstate &&
+ leaf_part_rri->ri_FdwRoutine != NULL &&
+ leaf_part_rri->ri_FdwRoutine->BeginForeignRouting != NULL)
+ leaf_part_rri->ri_FdwRoutine->BeginForeignRouting(mtstate,
+ leaf_part_rri,
+ partidx);
+
return leaf_part_rri;
}
***************
*** 603,609 **** ConvertPartitionTupleSlot(TupleConversionMap *map,
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
--- 609,616 ----
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(ModifyTableState *node,
! PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
***************
*** 632,637 **** ExecCleanupTupleRouting(PartitionTupleRouting *proute)
--- 639,653 ----
continue;
/*
+ * Allow any FDWs to shut down
+ */
+ if (node &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignRouting != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignRouting(node->ps.state,
+ resultRelInfo);
+
+ /*
* If this result rel is one of the UPDATE subplan result rels, let
* ExecEndPlan() close it. For INSERT or COPY,
* proute->subplan_partition_offsets will always be NULL. Note that
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 319,329 **** ExecInsert(ModifyTableState *mtstate,
Assert(resultRelInfo != NULL);
}
! /* 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;
--- 319,333 ----
Assert(resultRelInfo != NULL);
}
! /*
! * Verify the specified partition is a valid target for tuple routing
! * if not already done.
! */
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! CheckValidResultRel(resultRelInfo, CMD_INSERT);
! resultRelInfo->ri_PartitionIsValid = true;
! }
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
***************
*** 433,442 **** ExecInsert(ModifyTableState *mtstate,
/*
* insert into foreign table: let the FDW do it
*/
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! planSlot);
if (slot == NULL) /* "do nothing" */
return NULL;
--- 437,451 ----
/*
* insert into foreign table: let the FDW do it
*/
! if (resultRelInfo->ri_PartitionRoot)
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignRouting(estate,
! resultRelInfo,
! slot);
! else
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! planSlot);
if (slot == NULL) /* "do nothing" */
return NULL;
***************
*** 2311,2316 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 2320,2326 ----
{
List *rlist = (List *) lfirst(l);
+ resultRelInfo->ri_returningList = rlist;
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
***************
*** 2569,2575 **** ExecEndModifyTable(ModifyTableState *node)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node->mt_partition_tuple_routing);
/*
* Free the exprcontext
--- 2579,2585 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
/*
* Free the exprcontext
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 121,126 **** extern HeapTuple ConvertPartitionTupleSlot(TupleConversionMap *map,
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
--- 121,127 ----
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(ModifyTableState *node,
! PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 97,102 **** typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
--- 97,113 ----
typedef void (*EndForeignModify_function) (EState *estate,
ResultRelInfo *rinfo);
+ typedef void (*BeginForeignRouting_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo,
+ int partition_index);
+
+ typedef TupleTableSlot *(*ExecForeignRouting_function) (EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot);
+
+ typedef void (*EndForeignRouting_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
typedef int (*IsForeignRelUpdatable_function) (Relation rel);
typedef bool (*PlanDirectModify_function) (PlannerInfo *root,
***************
*** 204,209 **** typedef struct FdwRoutine
--- 215,223 ----
ExecForeignUpdate_function ExecForeignUpdate;
ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify;
+ BeginForeignRouting_function BeginForeignRouting;
+ ExecForeignRouting_function ExecForeignRouting;
+ EndForeignRouting_function EndForeignRouting;
IsForeignRelUpdatable_function IsForeignRelUpdatable;
PlanDirectModify_function PlanDirectModify;
BeginDirectModify_function BeginDirectModify;
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 403,408 **** typedef struct ResultRelInfo
--- 403,411 ----
/* list of WithCheckOption expr states */
List *ri_WithCheckOptionExprs;
+ /* list of RETURNING expressions */
+ List *ri_returningList;
+
/* array of constraint-checking expr states */
ExprState **ri_ConstraintExprs;
***************
*** 426,431 **** typedef struct ResultRelInfo
--- 429,437 ----
/* relation descriptor for root partitioned table */
Relation ri_PartitionRoot;
+
+ /* true if valid target for tuple routing */
+ bool ri_PartitionIsValid;
} ResultRelInfo;
/* ----------------
postgres-fdw-refactoring-WIP.patchtext/x-diff; name=postgres-fdw-refactoring-WIP.patchDownload
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 375,386 **** static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
--- 375,398 ----
static void create_cursor(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
+ static PgFdwModifyState *create_fdw_modify_state(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ int subplan_index,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs);
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot);
+ static int execute_prep_stmt(PgFdwModifyState *fmstate,
+ const char **p_values,
+ TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
+ static void finish_foreign_modify(PgFdwModifyState *fmstate);
static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
***************
*** 1678,1695 **** postgresBeginForeignModify(ModifyTableState *mtstate,
int eflags)
{
PgFdwModifyState *fmstate;
! EState *estate = mtstate->ps.state;
! CmdType operation = mtstate->operation;
! Relation rel = resultRelInfo->ri_RelationDesc;
! RangeTblEntry *rte;
! Oid userid;
! ForeignTable *table;
! UserMapping *user;
! AttrNumber n_params;
! Oid typefnoid;
! bool isvarlena;
! ListCell *lc;
! TupleDesc tupdesc = RelationGetDescr(rel);
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
--- 1690,1699 ----
int eflags)
{
PgFdwModifyState *fmstate;
! char *query;
! List *target_attrs;
! bool has_returning;
! List *retrieved_attrs;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
***************
*** 1698,1779 **** postgresBeginForeignModify(ModifyTableState *mtstate,
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
- /* Begin constructing PgFdwModifyState. */
- fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
- fmstate->rel = rel;
-
- /*
- * Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
- */
- rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
- userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
- /* Get info about foreign table. */
- table = GetForeignTable(RelationGetRelid(rel));
- user = GetUserMapping(userid, table->serverid);
-
- /* Open connection; report that we'll create a prepared statement. */
- fmstate->conn = GetConnection(user, true);
- fmstate->p_name = NULL; /* prepared statement not made yet */
-
/* Deconstruct fdw_private data. */
! fmstate->query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! fmstate->target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! fmstate->has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! fmstate->retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
!
! /* Create context for per-tuple temp workspace. */
! fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
! "postgres_fdw temporary data",
! ALLOCSET_SMALL_SIZES);
!
! /* Prepare for input conversion of RETURNING results. */
! if (fmstate->has_returning)
! fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
!
! /* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 1;
! fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
! fmstate->p_nums = 0;
!
! if (operation == CMD_UPDATE || operation == CMD_DELETE)
! {
! /* Find the ctid resjunk column in the subplan's result */
! Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
!
! fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
! "ctid");
! if (!AttributeNumberIsValid(fmstate->ctidAttno))
! elog(ERROR, "could not find junk ctid column");
!
! /* First transmittable parameter will be ctid */
! getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
!
! if (operation == CMD_INSERT || operation == CMD_UPDATE)
! {
! /* Set up for remaining transmittable parameters */
! foreach(lc, fmstate->target_attrs)
! {
! int attnum = lfirst_int(lc);
! Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
! Assert(!attr->attisdropped);
!
! getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
! }
!
! Assert(fmstate->p_nums <= n_params);
resultRelInfo->ri_FdwState = fmstate;
}
--- 1702,1725 ----
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/* Deconstruct fdw_private data. */
! query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
! /* Construct an execution state. */
! fmstate = create_fdw_modify_state(mtstate, resultRelInfo,
! mtstate->operation,
! subplan_index,
! query,
! target_attrs,
! has_returning,
! retrieved_attrs);
resultRelInfo->ri_FdwState = fmstate;
}
***************
*** 1790,1796 **** postgresExecForeignInsert(EState *estate,
{
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
const char **p_values;
- PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
--- 1736,1741 ----
***************
*** 1800,1840 **** postgresExecForeignInsert(EState *estate,
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate, NULL, slot);
! /*
! * Execute the prepared statement.
! */
! if (!PQsendQueryPrepared(fmstate->conn,
! fmstate->p_name,
! fmstate->p_nums,
! p_values,
! NULL,
! NULL,
! 0))
! pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
!
! /*
! * Get the result, and check for success.
! *
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_get_result(fmstate->conn, fmstate->query);
! if (PQresultStatus(res) !=
! (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
! pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
!
! /* Check number of rows affected, and fetch RETURNING tuple if any */
! if (fmstate->has_returning)
! {
! n_rows = PQntuples(res);
! if (n_rows > 0)
! store_returning_result(fmstate, slot, res);
! }
! else
! n_rows = atoi(PQcmdTuples(res));
!
! /* And clean up */
! PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
--- 1745,1752 ----
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate, NULL, slot);
! /* Execute the prepared statement and fetch RETURNING tuple if any */
! n_rows = execute_prep_stmt(fmstate, p_values, slot);
MemoryContextReset(fmstate->temp_cxt);
***************
*** 1856,1862 **** postgresExecForeignUpdate(EState *estate,
Datum datum;
bool isNull;
const char **p_values;
- PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
--- 1768,1773 ----
***************
*** 1876,1916 **** postgresExecForeignUpdate(EState *estate,
(ItemPointer) DatumGetPointer(datum),
slot);
! /*
! * Execute the prepared statement.
! */
! if (!PQsendQueryPrepared(fmstate->conn,
! fmstate->p_name,
! fmstate->p_nums,
! p_values,
! NULL,
! NULL,
! 0))
! pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
!
! /*
! * Get the result, and check for success.
! *
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_get_result(fmstate->conn, fmstate->query);
! if (PQresultStatus(res) !=
! (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
! pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
!
! /* Check number of rows affected, and fetch RETURNING tuple if any */
! if (fmstate->has_returning)
! {
! n_rows = PQntuples(res);
! if (n_rows > 0)
! store_returning_result(fmstate, slot, res);
! }
! else
! n_rows = atoi(PQcmdTuples(res));
!
! /* And clean up */
! PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
--- 1787,1794 ----
(ItemPointer) DatumGetPointer(datum),
slot);
! /* Execute the prepared statement and fetch RETURNING tuple if any */
! n_rows = execute_prep_stmt(fmstate, p_values, slot);
MemoryContextReset(fmstate->temp_cxt);
***************
*** 1932,1938 **** postgresExecForeignDelete(EState *estate,
Datum datum;
bool isNull;
const char **p_values;
- PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
--- 1810,1815 ----
***************
*** 1952,1992 **** postgresExecForeignDelete(EState *estate,
(ItemPointer) DatumGetPointer(datum),
NULL);
! /*
! * Execute the prepared statement.
! */
! if (!PQsendQueryPrepared(fmstate->conn,
! fmstate->p_name,
! fmstate->p_nums,
! p_values,
! NULL,
! NULL,
! 0))
! pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
!
! /*
! * Get the result, and check for success.
! *
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_get_result(fmstate->conn, fmstate->query);
! if (PQresultStatus(res) !=
! (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
! pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
!
! /* Check number of rows affected, and fetch RETURNING tuple if any */
! if (fmstate->has_returning)
! {
! n_rows = PQntuples(res);
! if (n_rows > 0)
! store_returning_result(fmstate, slot, res);
! }
! else
! n_rows = atoi(PQcmdTuples(res));
!
! /* And clean up */
! PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
--- 1829,1836 ----
(ItemPointer) DatumGetPointer(datum),
NULL);
! /* Execute the prepared statement and fetch RETURNING tuple if any */
! n_rows = execute_prep_stmt(fmstate, p_values, slot);
MemoryContextReset(fmstate->temp_cxt);
***************
*** 2008,2035 **** postgresEndForeignModify(EState *estate,
if (fmstate == NULL)
return;
! /* If we created a prepared statement, destroy it */
! if (fmstate->p_name)
! {
! char sql[64];
! PGresult *res;
!
! snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
!
! /*
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_exec_query(fmstate->conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
! PQclear(res);
! fmstate->p_name = NULL;
! }
!
! /* Release remote connection */
! ReleaseConnection(fmstate->conn);
! fmstate->conn = NULL;
}
/*
--- 1852,1859 ----
if (fmstate == NULL)
return;
! /* Destroy the execution state. */
! finish_foreign_modify(fmstate);
}
/*
***************
*** 3217,3222 **** close_cursor(PGconn *conn, unsigned int cursor_number)
--- 3041,3150 ----
}
/*
+ * create_fdw_modify_state
+ * Construct an execution state of a foreign insert/update/delete
+ * operation.
+ */
+ static PgFdwModifyState *
+ create_fdw_modify_state(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ int subplan_index,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs)
+ {
+ PgFdwModifyState *fmstate;
+ EState *estate = mtstate->ps.state;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Oid userid;
+ ForeignTable *table;
+ UserMapping *user;
+ AttrNumber n_params;
+ Oid typefnoid;
+ bool isvarlena;
+ ListCell *lc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+
+ /* Begin constructing PgFdwModifyState. */
+ fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
+ fmstate->rel = rel;
+
+ /*
+ * Identify which user to do the remote access as. This should match what
+ * ExecCheckRTEPerms() does.
+ */
+ rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ /* Get info about foreign table. */
+ table = GetForeignTable(RelationGetRelid(rel));
+ user = GetUserMapping(userid, table->serverid);
+
+ /* Open connection; report that we'll create a prepared statement. */
+ fmstate->conn = GetConnection(user, true);
+ fmstate->p_name = NULL; /* prepared statement not made yet */
+
+ /* Set remote query information. */
+ fmstate->query = query;
+ fmstate->target_attrs = target_attrs;
+ fmstate->has_returning = has_returning;
+ fmstate->retrieved_attrs = retrieved_attrs;
+
+ /* Create context for per-tuple temp workspace. */
+ fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+ "postgres_fdw temporary data",
+ ALLOCSET_SMALL_SIZES);
+
+ /* Prepare for input conversion of RETURNING results. */
+ if (fmstate->has_returning)
+ fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ /* Prepare for output conversion of parameters used in prepared stmt. */
+ n_params = list_length(fmstate->target_attrs) + 1;
+ fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
+ fmstate->p_nums = 0;
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* Find the ctid resjunk column in the subplan's result */
+ Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
+
+ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "ctid");
+ if (!AttributeNumberIsValid(fmstate->ctidAttno))
+ elog(ERROR, "could not find junk ctid column");
+
+ /* First transmittable parameter will be ctid */
+ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ /* Set up for remaining transmittable parameters */
+ foreach(lc, fmstate->target_attrs)
+ {
+ int attnum = lfirst_int(lc);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ Assert(!attr->attisdropped);
+
+ getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+ }
+
+ Assert(fmstate->p_nums <= n_params);
+
+ return fmstate;
+ }
+
+ /*
* prepare_foreign_modify
* Establish a prepared statement for execution of INSERT/UPDATE/DELETE
*/
***************
*** 3326,3331 **** convert_prep_stmt_params(PgFdwModifyState *fmstate,
--- 3254,3310 ----
}
/*
+ * execute_prep_stmt
+ * Execute the prepared statement and fetch RETURNING tuple if any
+ */
+ static int
+ execute_prep_stmt(PgFdwModifyState *fmstate,
+ const char **p_values,
+ TupleTableSlot *slot)
+ {
+ PGresult *res;
+ int n_rows;
+
+ /*
+ * Execute the prepared statement.
+ */
+ if (!PQsendQueryPrepared(fmstate->conn,
+ fmstate->p_name,
+ fmstate->p_nums,
+ p_values,
+ NULL,
+ NULL,
+ 0))
+ pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
+
+ /*
+ * Get the result, and check for success.
+ *
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_get_result(fmstate->conn, fmstate->query);
+ if (PQresultStatus(res) !=
+ (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
+
+ /* Check number of rows affected, and fetch RETURNING tuple if any */
+ if (fmstate->has_returning)
+ {
+ n_rows = PQntuples(res);
+ if (n_rows > 0)
+ store_returning_result(fmstate, slot, res);
+ }
+ else
+ n_rows = atoi(PQcmdTuples(res));
+
+ /* And clean up */
+ PQclear(res);
+
+ return n_rows;
+ }
+
+ /*
* store_returning_result
* Store the result of a RETURNING clause
*
***************
*** 3359,3364 **** store_returning_result(PgFdwModifyState *fmstate,
--- 3338,3376 ----
}
/*
+ * finish_foreign_modify
+ * Release resources for a foreign insert/update/delete operation.
+ */
+ static void
+ finish_foreign_modify(PgFdwModifyState *fmstate)
+ {
+ Assert(fmstate != NULL);
+
+ /* If we created a prepared statement, destroy it */
+ if (fmstate->p_name)
+ {
+ char sql[64];
+ PGresult *res;
+
+ snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
+
+ /*
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_exec_query(fmstate->conn, sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
+ PQclear(res);
+ fmstate->p_name = NULL;
+ }
+
+ /* Release remote connection */
+ ReleaseConnection(fmstate->conn);
+ fmstate->conn = NULL;
+ }
+
+ /*
* build_remote_returning
* Build a RETURNING targetlist of a remote query for performing an
* UPDATE/DELETE .. RETURNING on a join directly
Fujita-san,
On 2018/02/21 20:54, Etsuro Fujita wrote:
(2018/02/02 19:33), Etsuro Fujita wrote:
(2018/01/25 23:33), Stephen Frost wrote:
I'm afraid a good bit of this patch is now failing to apply. I don't
have much else to say except to echo the performance concern that Amit
Langote raised about expanding the inheritence tree twice.To address that concern, I'm thinking to redesign the patch so that it
wouldn't expand the tree at planning time anymore. I don't have any
clear solution for that yet, but what I have in mind now is to add new
FDW APIs to the executor, instead, so that the FDW could 1) create stuff
such as a query for remote INSERT as PlanForeignModify and 2)
initialize/end the remote INSERT operation as BeginForeignModify and
EndForeignModify, somewhere in the executor.New FDW APIs I would like to propose for that are:
Thanks for updating the proposal.
void
BeginForeignRouting(ModifyTableState *mtstate,
������������������� ResultRelInfo *resultRelInfo,
������������������� int partition_index);Prepare for a tuple-routing operation on a foreign table.� This is called
from ExecSetupPartitionTupleRouting and ExecInitPartitionInfo.
I wonder why partition_index needs to be made part of this API?
TupleTableSlot *
ExecForeignRouting(EState *estate,
������������������ ResultRelInfo *resultRelInfo,
������������������ TupleTableSlot *slot);Route one tuple to the foreign table.� This is called from ExecInsert.
void
EndForeignRouting(EState *estate,
����������������� ResultRelInfo *resultRelInfo);End the operation and release resources.� This is called from
ExecCleanupTupleRouting.Attached are WIP patches for that:
Patch postgres-fdw-refactoring-WIP.patch: refactoring patch for
postgres_fdw.c to reduce duplicate code.Patch foreign-routing-fdwapi-WIP.patch: main patch to add new FDW APIs,
which is created on top of patch postgres-fdw-refactoring-WIP.patch and
the lazy-initialization-of-partition-info patch [1].
Noticed a typo in the patch (s/parition/partition/g):
+ * Also let the FDW init itself if this parition is foreign.
+ * Also let the FDW init itself if this parition is foreign.
By this change we don't need to expand the inheritance tree at planning
time, so no need to worry about the performance concern.� Maybe I'm
missing something, though.� Early feedback would be greatly appreciated.
Perhaps an independent concern, but one thing I noticed is that it does
not seem to play well with the direct modification (update push-down)
feature. Now because updates (at least local, let's say) support
re-routing, I thought we'd be able move rows across servers via the local
server, but with direct modification we'd never get the chance. However,
since update tuple routing is triggered by partition constraint failure,
which we don't enforce for foreign tables/partitions anyway, I'm not sure
if we need to do anything about that, and even if we did, whether it
concerns this proposal.
That said, I saw in the changes to ExecSetupPartitionTupleRouting() that
BeginForeignRouting() is called for a foreign partition even if direct
modification might already have been set up. If direct modification is
set up, then ExecForeignRouting() will never get called, because we'd
never call ExecUpdate() or ExecInsert().
Thanks,
Amit
(2018/02/22 11:52), Amit Langote wrote:
On 2018/02/21 20:54, Etsuro Fujita wrote:
void
BeginForeignRouting(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo,
int partition_index);Prepare for a tuple-routing operation on a foreign table. This is called
from ExecSetupPartitionTupleRouting and ExecInitPartitionInfo.I wonder why partition_index needs to be made part of this API?
The reason for that is because I think the FDW might want to look at the
partition info stored in mtstate->mt_partition_tuple_routing for some
reason or other, such as parent_child_tupconv_maps and
child_parent_tupconv_maps, which are accessed with the given partition
index.
Patch foreign-routing-fdwapi-WIP.patch: main patch to add new FDW APIs,
which is created on top of patch postgres-fdw-refactoring-WIP.patch and
the lazy-initialization-of-partition-info patch [1].Noticed a typo in the patch (s/parition/partition/g):
+ * Also let the FDW init itself if this parition is foreign.
+ * Also let the FDW init itself if this parition is foreign.
Good catch! Will fix.
Perhaps an independent concern, but one thing I noticed is that it does
not seem to play well with the direct modification (update push-down)
feature. Now because updates (at least local, let's say) support
re-routing, I thought we'd be able move rows across servers via the local
server, but with direct modification we'd never get the chance. However,
since update tuple routing is triggered by partition constraint failure,
which we don't enforce for foreign tables/partitions anyway, I'm not sure
if we need to do anything about that, and even if we did, whether it
concerns this proposal.
Good point! Actually, update tuple routing we have in HEAD doesn't
allow re-routing tuples from foreign partitions even without direct
modification. Here is an example using postgres_fdw:
postgres=# create table pt (a int, b text) partition by list (a);
CREATE TABLE
postgres=# create table loct (a int check (a in (1)), b text);
CREATE TABLE
postgres=# create foreign table remp partition of pt for values in (1)
server loopback options (table_name 'loct');
CREATE FOREIGN TABLE
postgres=# create table locp partition of pt for values in (2);
CREATE TABLE
postgres=# insert into remp values (1, 'foo');
INSERT 0 1
postgres=# insert into locp values (2, 'bar');
INSERT 0 1
postgres=# select tableoid::regclass, * from pt;
tableoid | a | b
----------+---+-----
remp | 1 | foo
locp | 2 | bar
(2 rows)
postgres=# create function postgres_fdw_abs(int) returns int as $$begin
return abs($1); end$$ language plpgsql immutable;
CREATE FUNCTION
postgres=# explain verbose update pt set a = postgres_fdw_abs(a) + 1
where b = 'foo';
QUERY PLAN
--------------------------------------------------------------------------------
-------------
Update on public.pt (cost=100.00..154.54 rows=12 width=42)
Foreign Update on public.remp
Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1
Update on public.locp
-> Foreign Scan on public.remp (cost=100.00..127.15 rows=6 width=42)
Output: (postgres_fdw_abs(remp.a) + 1), remp.b, remp.ctid
Remote SQL: SELECT a, b, ctid FROM public.loct WHERE ((b =
'foo'::text)) FOR UPDATE
-> Seq Scan on public.locp (cost=0.00..27.39 rows=6 width=42)
Output: (postgres_fdw_abs(locp.a) + 1), locp.b, locp.ctid
Filter: (locp.b = 'foo'::text)
(10 rows)
postgres=# update pt set a = postgres_fdw_abs(a) + 1 where b = 'foo';
ERROR: new row for relation "loct" violates check constraint "loct_a_check"
DETAIL: Failing row contains (2, foo).
CONTEXT: Remote SQL command: UPDATE public.loct SET a = $2 WHERE ctid = $1
To be able to move tuples across foreign servers, I think we would first
have to do something to allow re-routing tuples from foreign partitions.
The patches I proposed hasn't done anything about that, so the patched
version would behave the same way as HEAD with/without direct modification.
That said, I saw in the changes to ExecSetupPartitionTupleRouting() that
BeginForeignRouting() is called for a foreign partition even if direct
modification might already have been set up. If direct modification is
set up, then ExecForeignRouting() will never get called, because we'd
never call ExecUpdate() or ExecInsert().
Yeah, but I am thinking to leave the support for re-routing tuples
across foreign servers for another patch.
One difference between HEAD and the patched version would be: we can
re-route tuples from a plain partition to a foreign partition if the
foreign partition supports this tuple-routing API. Here is an example
using the above data:
postgres=# select tableoid::regclass, * from pt;
tableoid | a | b
----------+---+-----
remp | 1 | foo
locp | 2 | bar
(2 rows)
postgres=# update pt set a = 1 where b = 'bar';
UPDATE 1
postgres=# select tableoid::regclass, * from pt;
tableoid | a | b
----------+---+-----
remp | 1 | foo
remp | 1 | bar
(2 rows)
This would introduce an asymmetry (we can move tuples from plain
partitions to foreign partitions, but the reverse is not true), but I am
thinking that it would be probably okay to document about that.
Thank you for the review!
Best regards,
Etsuro Fujita
Fujita-san,
On Thu, Feb 22, 2018 at 8:49 PM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
(2018/02/22 11:52), Amit Langote wrote:
I wonder why partition_index needs to be made part of this API?
The reason for that is because I think the FDW might want to look at the
partition info stored in mtstate->mt_partition_tuple_routing for some reason
or other, such as parent_child_tupconv_maps and child_parent_tupconv_maps,
which are accessed with the given partition index.
I see. I guess that makes sense.
Perhaps an independent concern, but one thing I noticed is that it does
not seem to play well with the direct modification (update push-down)
feature. Now because updates (at least local, let's say) support
re-routing, I thought we'd be able move rows across servers via the local
server, but with direct modification we'd never get the chance. However,
since update tuple routing is triggered by partition constraint failure,
which we don't enforce for foreign tables/partitions anyway, I'm not sure
if we need to do anything about that, and even if we did, whether it
concerns this proposal.Good point! Actually, update tuple routing we have in HEAD doesn't allow
re-routing tuples from foreign partitions even without direct modification.
Here is an example using postgres_fdw:postgres=# create table pt (a int, b text) partition by list (a);
CREATE TABLE
postgres=# create table loct (a int check (a in (1)), b text);
CREATE TABLE
postgres=# create foreign table remp partition of pt for values in (1)
server loopback options (table_name 'loct');
CREATE FOREIGN TABLE
postgres=# create table locp partition of pt for values in (2);
CREATE TABLE
postgres=# insert into remp values (1, 'foo');
INSERT 0 1
postgres=# insert into locp values (2, 'bar');
INSERT 0 1
postgres=# select tableoid::regclass, * from pt;
tableoid | a | b
----------+---+-----
remp | 1 | foo
locp | 2 | bar
(2 rows)postgres=# create function postgres_fdw_abs(int) returns int as $$begin
return abs($1); end$$ language plpgsql immutable;
CREATE FUNCTION
postgres=# explain verbose update pt set a = postgres_fdw_abs(a) + 1 where b
= 'foo';
QUERY PLAN--------------------------------------------------------------------------------
-------------
Update on public.pt (cost=100.00..154.54 rows=12 width=42)
Foreign Update on public.remp
Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1
Update on public.locp
-> Foreign Scan on public.remp (cost=100.00..127.15 rows=6 width=42)
Output: (postgres_fdw_abs(remp.a) + 1), remp.b, remp.ctid
Remote SQL: SELECT a, b, ctid FROM public.loct WHERE ((b =
'foo'::text)) FOR UPDATE
-> Seq Scan on public.locp (cost=0.00..27.39 rows=6 width=42)
Output: (postgres_fdw_abs(locp.a) + 1), locp.b, locp.ctid
Filter: (locp.b = 'foo'::text)
(10 rows)postgres=# update pt set a = postgres_fdw_abs(a) + 1 where b = 'foo';
ERROR: new row for relation "loct" violates check constraint "loct_a_check"
DETAIL: Failing row contains (2, foo).
CONTEXT: Remote SQL command: UPDATE public.loct SET a = $2 WHERE ctid = $1To be able to move tuples across foreign servers, I think we would first
have to do something to allow re-routing tuples from foreign partitions.
The patches I proposed hasn't done anything about that, so the patched
version would behave the same way as HEAD with/without direct modification.
Yes.
As I said, since update re-routing is triggered by partition
constraint failure for the new row and we don't check (any)
constraints for foreign tables, that means re-routing won't occur for
foreign partitions.
That said, I saw in the changes to ExecSetupPartitionTupleRouting() that
BeginForeignRouting() is called for a foreign partition even if direct
modification might already have been set up. If direct modification is
set up, then ExecForeignRouting() will never get called, because we'd
never call ExecUpdate() or ExecInsert().Yeah, but I am thinking to leave the support for re-routing tuples across
foreign servers for another patch.One difference between HEAD and the patched version would be: we can
re-route tuples from a plain partition to a foreign partition if the foreign
partition supports this tuple-routing API. Here is an example using the
above data:postgres=# select tableoid::regclass, * from pt;
tableoid | a | b
----------+---+-----
remp | 1 | foo
locp | 2 | bar
(2 rows)postgres=# update pt set a = 1 where b = 'bar';
UPDATE 1
postgres=# select tableoid::regclass, * from pt;
tableoid | a | b
----------+---+-----
remp | 1 | foo
remp | 1 | bar
(2 rows)This would introduce an asymmetry (we can move tuples from plain partitions
to foreign partitions, but the reverse is not true), but I am thinking that
it would be probably okay to document about that.
I see. Thanks for the explanation.
About just documenting the asymmetry you mentioned that's caused by
the fact that we don't enforce constraints on foreign tables, I
started wondering if we shouldn't change our stance on the matter wrt
"partition" constraints? But, admittedly, that's a topic for a
different thread.
Thanks,
Amit
(2018/02/23 16:38), Amit Langote wrote:
On Thu, Feb 22, 2018 at 8:49 PM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:This would introduce an asymmetry (we can move tuples from plain partitions
to foreign partitions, but the reverse is not true), but I am thinking that
it would be probably okay to document about that.
About just documenting the asymmetry you mentioned that's caused by
the fact that we don't enforce constraints on foreign tables, I
started wondering if we shouldn't change our stance on the matter wrt
"partition" constraints?
I'm not sure that it's a good idea to make an exception in that case.
Another concern is triggers on the remote side; those might change the
row so that the partition constraint of the containing partition is no
longer satisfied.
But, admittedly, that's a topic for a
different thread.
OK, I'll leave that for another patch.
Will post a new version. Thanks for the comments!
Best regards,
Etsuro Fujita
(2018/02/21 20:54), Etsuro Fujita wrote:
void
BeginForeignRouting();Prepare for a tuple-routing operation on a foreign table. This is called
from ExecSetupPartitionTupleRouting and ExecInitPartitionInfo.
I modified execPartition.c so that this callback routine is called from
a single function that I added to execPartition.c and it is called the
first time the foreign partition is chose as the target partition to
route a tuple to. That removes CheckValidResultRel, the
tuple-conversion setup, and the FDW initialization for each UPDATE
subplan from ExecSetupPartitionTupleRouting, so it would minimize the
possibly-useless overhead in doing that function.
Changes other than that are:
* Fixed typo and revised code/comments
* Added more regression tests
* Added docs
Attached is a new version of the patch set.
Best regards,
Etsuro Fujita
Attachments:
postgres-fdw-refactoring-1.patchtext/x-diff; name=postgres-fdw-refactoring-1.patchDownload
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 375,386 **** static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
--- 375,398 ----
static void create_cursor(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
+ static PgFdwModifyState *create_fdw_modify_state(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ int subplan_index,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs);
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot);
+ static int execute_prep_stmt(PgFdwModifyState *fmstate,
+ const char **p_values,
+ TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
+ static void finish_foreign_modify(PgFdwModifyState *fmstate);
static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
***************
*** 1678,1695 **** postgresBeginForeignModify(ModifyTableState *mtstate,
int eflags)
{
PgFdwModifyState *fmstate;
! EState *estate = mtstate->ps.state;
! CmdType operation = mtstate->operation;
! Relation rel = resultRelInfo->ri_RelationDesc;
! RangeTblEntry *rte;
! Oid userid;
! ForeignTable *table;
! UserMapping *user;
! AttrNumber n_params;
! Oid typefnoid;
! bool isvarlena;
! ListCell *lc;
! TupleDesc tupdesc = RelationGetDescr(rel);
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
--- 1690,1699 ----
int eflags)
{
PgFdwModifyState *fmstate;
! char *query;
! List *target_attrs;
! bool has_returning;
! List *retrieved_attrs;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
***************
*** 1698,1779 **** postgresBeginForeignModify(ModifyTableState *mtstate,
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
- /* Begin constructing PgFdwModifyState. */
- fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
- fmstate->rel = rel;
-
- /*
- * Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
- */
- rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
- userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
- /* Get info about foreign table. */
- table = GetForeignTable(RelationGetRelid(rel));
- user = GetUserMapping(userid, table->serverid);
-
- /* Open connection; report that we'll create a prepared statement. */
- fmstate->conn = GetConnection(user, true);
- fmstate->p_name = NULL; /* prepared statement not made yet */
-
/* Deconstruct fdw_private data. */
! fmstate->query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! fmstate->target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! fmstate->has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! fmstate->retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
!
! /* Create context for per-tuple temp workspace. */
! fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
! "postgres_fdw temporary data",
! ALLOCSET_SMALL_SIZES);
!
! /* Prepare for input conversion of RETURNING results. */
! if (fmstate->has_returning)
! fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
!
! /* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 1;
! fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
! fmstate->p_nums = 0;
!
! if (operation == CMD_UPDATE || operation == CMD_DELETE)
! {
! /* Find the ctid resjunk column in the subplan's result */
! Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
!
! fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
! "ctid");
! if (!AttributeNumberIsValid(fmstate->ctidAttno))
! elog(ERROR, "could not find junk ctid column");
!
! /* First transmittable parameter will be ctid */
! getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
!
! if (operation == CMD_INSERT || operation == CMD_UPDATE)
! {
! /* Set up for remaining transmittable parameters */
! foreach(lc, fmstate->target_attrs)
! {
! int attnum = lfirst_int(lc);
! Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
! Assert(!attr->attisdropped);
!
! getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
! }
!
! Assert(fmstate->p_nums <= n_params);
resultRelInfo->ri_FdwState = fmstate;
}
--- 1702,1726 ----
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/* Deconstruct fdw_private data. */
! query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
! /* Construct an execution state. */
! fmstate = create_fdw_modify_state(mtstate,
! resultRelInfo,
! mtstate->operation,
! subplan_index,
! query,
! target_attrs,
! has_returning,
! retrieved_attrs);
resultRelInfo->ri_FdwState = fmstate;
}
***************
*** 1790,1796 **** postgresExecForeignInsert(EState *estate,
{
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
const char **p_values;
- PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
--- 1737,1742 ----
***************
*** 1800,1840 **** postgresExecForeignInsert(EState *estate,
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate, NULL, slot);
! /*
! * Execute the prepared statement.
! */
! if (!PQsendQueryPrepared(fmstate->conn,
! fmstate->p_name,
! fmstate->p_nums,
! p_values,
! NULL,
! NULL,
! 0))
! pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
!
! /*
! * Get the result, and check for success.
! *
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_get_result(fmstate->conn, fmstate->query);
! if (PQresultStatus(res) !=
! (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
! pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
!
! /* Check number of rows affected, and fetch RETURNING tuple if any */
! if (fmstate->has_returning)
! {
! n_rows = PQntuples(res);
! if (n_rows > 0)
! store_returning_result(fmstate, slot, res);
! }
! else
! n_rows = atoi(PQcmdTuples(res));
!
! /* And clean up */
! PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
--- 1746,1753 ----
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate, NULL, slot);
! /* Execute the prepared statement and fetch RETURNING tuple if any */
! n_rows = execute_prep_stmt(fmstate, p_values, slot);
MemoryContextReset(fmstate->temp_cxt);
***************
*** 1856,1862 **** postgresExecForeignUpdate(EState *estate,
Datum datum;
bool isNull;
const char **p_values;
- PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
--- 1769,1774 ----
***************
*** 1876,1916 **** postgresExecForeignUpdate(EState *estate,
(ItemPointer) DatumGetPointer(datum),
slot);
! /*
! * Execute the prepared statement.
! */
! if (!PQsendQueryPrepared(fmstate->conn,
! fmstate->p_name,
! fmstate->p_nums,
! p_values,
! NULL,
! NULL,
! 0))
! pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
!
! /*
! * Get the result, and check for success.
! *
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_get_result(fmstate->conn, fmstate->query);
! if (PQresultStatus(res) !=
! (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
! pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
!
! /* Check number of rows affected, and fetch RETURNING tuple if any */
! if (fmstate->has_returning)
! {
! n_rows = PQntuples(res);
! if (n_rows > 0)
! store_returning_result(fmstate, slot, res);
! }
! else
! n_rows = atoi(PQcmdTuples(res));
!
! /* And clean up */
! PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
--- 1788,1795 ----
(ItemPointer) DatumGetPointer(datum),
slot);
! /* Execute the prepared statement and fetch RETURNING tuple if any */
! n_rows = execute_prep_stmt(fmstate, p_values, slot);
MemoryContextReset(fmstate->temp_cxt);
***************
*** 1932,1938 **** postgresExecForeignDelete(EState *estate,
Datum datum;
bool isNull;
const char **p_values;
- PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
--- 1811,1816 ----
***************
*** 1952,1992 **** postgresExecForeignDelete(EState *estate,
(ItemPointer) DatumGetPointer(datum),
NULL);
! /*
! * Execute the prepared statement.
! */
! if (!PQsendQueryPrepared(fmstate->conn,
! fmstate->p_name,
! fmstate->p_nums,
! p_values,
! NULL,
! NULL,
! 0))
! pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
!
! /*
! * Get the result, and check for success.
! *
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_get_result(fmstate->conn, fmstate->query);
! if (PQresultStatus(res) !=
! (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
! pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
!
! /* Check number of rows affected, and fetch RETURNING tuple if any */
! if (fmstate->has_returning)
! {
! n_rows = PQntuples(res);
! if (n_rows > 0)
! store_returning_result(fmstate, slot, res);
! }
! else
! n_rows = atoi(PQcmdTuples(res));
!
! /* And clean up */
! PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
--- 1830,1837 ----
(ItemPointer) DatumGetPointer(datum),
NULL);
! /* Execute the prepared statement and fetch RETURNING tuple if any */
! n_rows = execute_prep_stmt(fmstate, p_values, slot);
MemoryContextReset(fmstate->temp_cxt);
***************
*** 2008,2035 **** postgresEndForeignModify(EState *estate,
if (fmstate == NULL)
return;
! /* If we created a prepared statement, destroy it */
! if (fmstate->p_name)
! {
! char sql[64];
! PGresult *res;
!
! snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
!
! /*
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_exec_query(fmstate->conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
! PQclear(res);
! fmstate->p_name = NULL;
! }
!
! /* Release remote connection */
! ReleaseConnection(fmstate->conn);
! fmstate->conn = NULL;
}
/*
--- 1853,1860 ----
if (fmstate == NULL)
return;
! /* Destroy the execution state. */
! finish_foreign_modify(fmstate);
}
/*
***************
*** 3217,3222 **** close_cursor(PGconn *conn, unsigned int cursor_number)
--- 3042,3151 ----
}
/*
+ * create_fdw_modify_state
+ * Construct an execution state of a foreign insert/update/delete
+ * operation.
+ */
+ static PgFdwModifyState *
+ create_fdw_modify_state(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ int subplan_index,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs)
+ {
+ PgFdwModifyState *fmstate;
+ EState *estate = mtstate->ps.state;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Oid userid;
+ ForeignTable *table;
+ UserMapping *user;
+ AttrNumber n_params;
+ Oid typefnoid;
+ bool isvarlena;
+ ListCell *lc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+
+ /* Begin constructing PgFdwModifyState. */
+ fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
+ fmstate->rel = rel;
+
+ /*
+ * Identify which user to do the remote access as. This should match what
+ * ExecCheckRTEPerms() does.
+ */
+ rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ /* Get info about foreign table. */
+ table = GetForeignTable(RelationGetRelid(rel));
+ user = GetUserMapping(userid, table->serverid);
+
+ /* Open connection; report that we'll create a prepared statement. */
+ fmstate->conn = GetConnection(user, true);
+ fmstate->p_name = NULL; /* prepared statement not made yet */
+
+ /* Set remote query information. */
+ fmstate->query = query;
+ fmstate->target_attrs = target_attrs;
+ fmstate->has_returning = has_returning;
+ fmstate->retrieved_attrs = retrieved_attrs;
+
+ /* Create context for per-tuple temp workspace. */
+ fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+ "postgres_fdw temporary data",
+ ALLOCSET_SMALL_SIZES);
+
+ /* Prepare for input conversion of RETURNING results. */
+ if (fmstate->has_returning)
+ fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ /* Prepare for output conversion of parameters used in prepared stmt. */
+ n_params = list_length(fmstate->target_attrs) + 1;
+ fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
+ fmstate->p_nums = 0;
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ /* Find the ctid resjunk column in the subplan's result */
+ Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
+
+ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "ctid");
+ if (!AttributeNumberIsValid(fmstate->ctidAttno))
+ elog(ERROR, "could not find junk ctid column");
+
+ /* First transmittable parameter will be ctid */
+ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ /* Set up for remaining transmittable parameters */
+ foreach(lc, fmstate->target_attrs)
+ {
+ int attnum = lfirst_int(lc);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ Assert(!attr->attisdropped);
+
+ getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+ }
+
+ Assert(fmstate->p_nums <= n_params);
+
+ return fmstate;
+ }
+
+ /*
* prepare_foreign_modify
* Establish a prepared statement for execution of INSERT/UPDATE/DELETE
*/
***************
*** 3326,3331 **** convert_prep_stmt_params(PgFdwModifyState *fmstate,
--- 3255,3311 ----
}
/*
+ * execute_prep_stmt
+ * Execute the prepared statement and fetch RETURNING tuple if any
+ */
+ static int
+ execute_prep_stmt(PgFdwModifyState *fmstate,
+ const char **p_values,
+ TupleTableSlot *slot)
+ {
+ PGresult *res;
+ int n_rows;
+
+ /*
+ * Execute the prepared statement.
+ */
+ if (!PQsendQueryPrepared(fmstate->conn,
+ fmstate->p_name,
+ fmstate->p_nums,
+ p_values,
+ NULL,
+ NULL,
+ 0))
+ pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
+
+ /*
+ * Get the result, and check for success.
+ *
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_get_result(fmstate->conn, fmstate->query);
+ if (PQresultStatus(res) !=
+ (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
+
+ /* Check number of rows affected, and fetch RETURNING tuple if any */
+ if (fmstate->has_returning)
+ {
+ n_rows = PQntuples(res);
+ if (n_rows > 0)
+ store_returning_result(fmstate, slot, res);
+ }
+ else
+ n_rows = atoi(PQcmdTuples(res));
+
+ /* And clean up */
+ PQclear(res);
+
+ return n_rows;
+ }
+
+ /*
* store_returning_result
* Store the result of a RETURNING clause
*
***************
*** 3359,3364 **** store_returning_result(PgFdwModifyState *fmstate,
--- 3339,3377 ----
}
/*
+ * finish_foreign_modify
+ * Release resources for a foreign insert/update/delete operation.
+ */
+ static void
+ finish_foreign_modify(PgFdwModifyState *fmstate)
+ {
+ Assert(fmstate != NULL);
+
+ /* If we created a prepared statement, destroy it */
+ if (fmstate->p_name)
+ {
+ char sql[64];
+ PGresult *res;
+
+ snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
+
+ /*
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_exec_query(fmstate->conn, sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
+ PQclear(res);
+ fmstate->p_name = NULL;
+ }
+
+ /* Release remote connection */
+ ReleaseConnection(fmstate->conn);
+ fmstate->conn = NULL;
+ }
+
+ /*
* build_remote_returning
* Build a RETURNING targetlist of a remote query for performing an
* UPDATE/DELETE .. RETURNING on a join directly
foreign-routing-fdwapi-1.patchtext/x-diff; name=foreign-routing-fdwapi-1.patchDownload
*** 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;
***************
*** 342,351 **** SELECT tableoid::regclass, * FROM p2;
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
--- 342,351 ----
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to foreign table "p1"
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to foreign table "p1"
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7364,7369 **** NOTICE: drop cascades to foreign table bar2
--- 7364,7510 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ create table itrtest (a int, b int, c text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b int primary key, c text);
+ create table loct2 (b int primary key, c text, a int check (a in (2)));
+ create foreign table remp1 (a int check (a in (1)), b int, c text) server loopback options (table_name 'loct1');
+ create foreign table remp2 (b int, c text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+ insert into itrtest values (1, 1, 'foo');
+ insert into itrtest values (1, 2, 'bar') returning *;
+ a | b | c
+ ---+---+-----
+ 1 | 2 | bar
+ (1 row)
+
+ insert into itrtest values (2, 1, 'baz') returning *;
+ a | b | c
+ ---+---+-----
+ 2 | 1 | baz
+ (1 row)
+
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ remp2 | 2 | 1 | baz
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | c | a
+ ----------+---+-----+---
+ remp2 | 1 | baz | 2
+ (1 row)
+
+ insert into itrtest values (2, 1, 'baz');
+ ERROR: duplicate key value violates unique constraint "loct2_pkey"
+ DETAIL: Key (b)=(1) already exists.
+ CONTEXT: Remote SQL command: INSERT INTO public.loct2(b, c, a) VALUES ($1, $2, $3)
+ insert into itrtest values (2, 1, 'baz') on conflict do nothing;
+ insert into itrtest values (2, 2, 'qux') on conflict do nothing returning *;
+ a | b | c
+ ---+---+-----
+ 2 | 2 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ remp2 | 2 | 1 | baz
+ remp2 | 2 | 2 | qux
+ (4 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | c | a
+ ----------+---+-----+---
+ remp2 | 1 | baz | 2
+ remp2 | 2 | qux | 2
+ (2 rows)
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+ create table utrtest (a int, b int, c text) partition by list (a);
+ create table loct (a int check (a in (1)), b int, c text);
+ create foreign table remp (a int check (a in (1)), b int, c text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b int, c text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+ insert into utrtest values (1, 1, 'foo');
+ insert into utrtest values (2, 2, 'qux');
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp | 1 | 1 | foo
+ locp | 2 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp | 1 | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ locp | 2 | 2 | qux
+ (1 row)
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where c = 'foo' returning *;
+ ERROR: new row for relation "loct" violates check constraint "loct_a_check"
+ DETAIL: Failing row contains (2, 1, foo).
+ CONTEXT: Remote SQL command: UPDATE public.loct SET a = 2 WHERE ((c = 'foo'::text)) RETURNING a, b, c
+ -- But the reverse is allowed
+ update utrtest set a = 1 where c = 'qux' returning *;
+ a | b | c
+ ---+---+-----
+ 1 | 2 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp | 1 | 1 | foo
+ remp | 1 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp | 1 | 1 | foo
+ remp | 1 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b | c
+ ----------+---+---+---
+ (0 rows)
+
+ drop table utrtest;
+ drop table loct;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 319,324 **** static TupleTableSlot *postgresExecForeignDelete(EState *estate,
--- 319,332 ----
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
+ static void postgresBeginForeignRouting(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ int partition_index);
+ static TupleTableSlot *postgresExecForeignRouting(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot);
+ static void postgresEndForeignRouting(EState *estate,
+ ResultRelInfo *resultRelInfo);
static int postgresIsForeignRelUpdatable(Relation rel);
static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
***************
*** 473,478 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 481,489 ----
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
+ routine->BeginForeignRouting = postgresBeginForeignRouting;
+ routine->ExecForeignRouting = postgresExecForeignRouting;
+ routine->EndForeignRouting = postgresEndForeignRouting;
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
routine->PlanDirectModify = postgresPlanDirectModify;
routine->BeginDirectModify = postgresBeginDirectModify;
***************
*** 1858,1863 **** postgresEndForeignModify(EState *estate,
--- 1869,1987 ----
}
/*
+ * postgresBeginForeignRouting
+ * Begin a tuple routing operation on a foreign table
+ */
+ static void
+ postgresBeginForeignRouting(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ int partition_index)
+ {
+ PgFdwModifyState *fmstate;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Query *query;
+ PlannerInfo *root;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int attnum;
+ StringInfoData sql;
+ List *targetAttrs = NIL;
+ List *retrieved_attrs = NIL;
+ bool doNothing = false;
+
+ initStringInfo(&sql);
+
+ /* Set up largely-dummy planner state */
+ rte = makeNode(RangeTblEntry);
+ rte->rtekind = RTE_RELATION;
+ rte->relid = RelationGetRelid(rel);
+ rte->relkind = RELKIND_FOREIGN_TABLE;
+ query = makeNode(Query);
+ query->commandType = CMD_INSERT;
+ query->resultRelation = 1;
+ query->rtable = list_make1(rte);
+ root = makeNode(PlannerInfo);
+ root->parse = query;
+
+ /* We transmit all columns that are defined in the foreign table. */
+ for (attnum = 1; attnum <= tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (!attr->attisdropped)
+ targetAttrs = lappend_int(targetAttrs, attnum);
+ }
+
+ /* We only support DO NOTHING without an inference specification. */
+ if (mtstate->mt_onconflict == ONCONFLICT_NOTHING)
+ doNothing = true;
+ else if (mtstate->mt_onconflict != ONCONFLICT_NONE)
+ elog(ERROR, "unexpected ON CONFLICT specification: %d",
+ (int) mtstate->mt_onconflict);
+
+ /* Construct the SQL command string. */
+ deparseInsertSql(&sql, root, 1, rel, targetAttrs, doNothing,
+ resultRelInfo->ri_returningList, &retrieved_attrs);
+
+ /* Construct an execution state. */
+ fmstate = create_fdw_modify_state(mtstate,
+ resultRelInfo,
+ CMD_INSERT,
+ 0, /* dummy subplan index */
+ sql.data,
+ targetAttrs,
+ retrieved_attrs != NIL,
+ retrieved_attrs);
+
+ resultRelInfo->ri_FdwState = fmstate;
+ }
+
+ /*
+ * postgresExecForeignRouting
+ * Route one tuple to a foreign table
+ */
+ static TupleTableSlot *
+ postgresExecForeignRouting(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+ const char **p_values;
+ int n_rows;
+
+ /* Set up the prepared statement on the remote server, if we didn't yet */
+ if (!fmstate->p_name)
+ prepare_foreign_modify(fmstate);
+
+ /* Convert parameters needed by prepared statement to text form */
+ p_values = convert_prep_stmt_params(fmstate, NULL, slot);
+
+ /* Execute the prepared statement and fetch RETURNING tuple if any */
+ n_rows = execute_prep_stmt(fmstate, p_values, slot);
+
+ MemoryContextReset(fmstate->temp_cxt);
+
+ /* Return NULL if nothing was inserted on the remote end */
+ return (n_rows > 0) ? slot : NULL;
+ }
+
+ /*
+ * postgresEndForeignRouting
+ * Finish a tuple routing operation on a foreign table
+ */
+ static void
+ postgresEndForeignRouting(EState *estate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+
+ Assert(fmstate != NULL);
+
+ /* Destroy the execution state. */
+ finish_foreign_modify(fmstate);
+ }
+
+ /*
* postgresIsForeignRelUpdatable
* Determine whether a foreign table supports INSERT, UPDATE and/or
* DELETE.
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1759,1764 **** drop table loct1;
--- 1759,1823 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ create table itrtest (a int, b int, c text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b int primary key, c text);
+ create table loct2 (b int primary key, c text, a int check (a in (2)));
+ create foreign table remp1 (a int check (a in (1)), b int, c text) server loopback options (table_name 'loct1');
+ create foreign table remp2 (b int, c text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+
+ insert into itrtest values (1, 1, 'foo');
+ insert into itrtest values (1, 2, 'bar') returning *;
+ insert into itrtest values (2, 1, 'baz') returning *;
+
+ select tableoid::regclass, * FROM itrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ insert into itrtest values (2, 1, 'baz');
+ insert into itrtest values (2, 1, 'baz') on conflict do nothing;
+ insert into itrtest values (2, 2, 'qux') on conflict do nothing returning *;
+
+ select tableoid::regclass, * FROM itrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+
+ create table utrtest (a int, b int, c text) partition by list (a);
+ create table loct (a int check (a in (1)), b int, c text);
+ create foreign table remp (a int check (a in (1)), b int, c text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b int, c text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+
+ insert into utrtest values (1, 1, 'foo');
+ insert into utrtest values (2, 2, 'qux');
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where c = 'foo' returning *;
+
+ -- But the reverse is allowed
+ update utrtest set a = 1 where c = 'qux' returning *;
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ drop table utrtest;
+ drop table loct;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 690,695 **** EndForeignModify(EState *estate,
--- 690,795 ----
</para>
<para>
+ Tuples inserted into partitioned tables are routed to partitions. If
+ an FDW supports routable foreign-table partitions, it should also
+ provide the following callback functions.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ BeginForeignRouting(ModifyTableState *mtstate,
+ ResultRelInfo *rinfo,
+ int partition_index);
+ </programlisting>
+
+ Begin executing a tuple routing operation on a foreign table. This
+ routine is called the first time the foreign table is chosen as the
+ partition to route an inserted tuple to. It should perform any
+ initialization needed prior to the actual tuple routing. Subsequently,
+ <function>ExecForeignRouting</function> will be called for each tuple
+ to be routed.
+ </para>
+
+ <para>
+ <literal>mtstate</literal> is the overall state of the
+ <structname>ModifyTable</structname> plan node being executed; global data about
+ the plan and execution state is available via this structure.
+ <literal>rinfo</literal> is the <structname>ResultRelInfo</structname> struct describing
+ the target foreign table. (The <structfield>ri_FdwState</structfield> field of
+ <structname>ResultRelInfo</structname> is available for the FDW to store any
+ private state it needs for this operation.)
+ <literal>partition_index</literal> identifies which partition of
+ a partitioned table this is, counting from zero; use this if you want
+ to index into substructures of
+ <literal>mtstate->mt_partition_tuple_routing</literal>, which contain
+ information about each partition of the partitioned table.
+ </para>
+
+ <para>
+ If the <function>BeginForeignRouting</function> pointer is set to
+ <literal>NULL</literal>, no action is taken the first time the foreign
+ table is chosen as the partition.
+ </para>
+
+ <para>
+ <programlisting>
+ TupleTableSlot *
+ ExecForeignRouting(EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot);
+ </programlisting>
+
+ Route one tuple to the foreign table.
+ <literal>estate</literal> is global execution state for the query.
+ <literal>rinfo</literal> is the <structname>ResultRelInfo</structname> struct describing
+ the target foreign table.
+ <literal>slot</literal> contains the tuple to be routed; it will match the
+ row-type definition of the foreign table.
+ </para>
+
+ <para>
+ The return value is either a slot containing the data that was actually
+ routed (this might differ from the data supplied, for example as a
+ result of trigger actions), or NULL if no row was actually routed
+ (again, typically as a result of triggers). The passed-in
+ <literal>slot</literal> can be re-used for this purpose.
+ </para>
+
+ <para>
+ The data in the returned slot is used only if the <command>INSERT</command>
+ query has a <literal>RETURNING</literal> clause or the foreign table has
+ an <literal>AFTER ROW</literal> trigger. Triggers require all columns, but the
+ FDW could choose to optimize away returning some or all columns depending
+ on the contents of the <literal>RETURNING</literal> clause. Regardless, some
+ slot must be returned to indicate success, or the query's reported row
+ count will be wrong.
+ </para>
+
+ <para>
+ If the <function>ExecForeignRouting</function> pointer is set to
+ <literal>NULL</literal>, attempts to route rows to the foreign table will fail
+ with an error message.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ EndForeignRouting(EState *estate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ End the tuple routing and release resources. It is normally not important
+ to release palloc'd memory, but for example open files and connections
+ to remote servers should be cleaned up.
+ </para>
+
+ <para>
+ If the <function>EndForeignRouting</function> pointer is set to
+ <literal>NULL</literal>, no action is taken during executor shutdown.
+ </para>
+
+ <para>
<programlisting>
int
IsForeignRelUpdatable(Relation rel);
***************
*** 709,715 **** IsForeignRelUpdatable(Relation rel);
<literal>NULL</literal>, foreign tables are assumed to be insertable, updatable,
or deletable if the FDW provides <function>ExecForeignInsert</function>,
<function>ExecForeignUpdate</function>, or <function>ExecForeignDelete</function>
! respectively. This function is only needed if the FDW supports some
tables that are updatable and some that are not. (Even then, it's
permissible to throw an error in the execution routine instead of
checking in this function. However, this function is used to determine
--- 809,817 ----
<literal>NULL</literal>, foreign tables are assumed to be insertable, updatable,
or deletable if the FDW provides <function>ExecForeignInsert</function>,
<function>ExecForeignUpdate</function>, or <function>ExecForeignDelete</function>
! respectively. In that case, foreign tables are also assumed to be
! routable if the FDW provides <function>ExecForeignRouting</function>.
! This function is only needed if the FDW supports some
tables that are updatable and some that are not. (Even then, it's
permissible to throw an error in the execution routine instead of
checking in this function. However, this function is used to determine
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 291,296 **** UPDATE <replaceable class="parameter">count</replaceable>
--- 291,299 ----
concurrent <command>UPDATE</command> or <command>DELETE</command> on the
same row may miss this row. For details see the section
<xref linkend="ddl-partitioning-declarative-limitations"/>.
+ Currently, it is not allowed to move a row from a partition that is a
+ foreign table to another, but the reverse is allowed if the foreign table
+ is routable.
</para>
</refsect1>
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2615,2625 **** CopyFrom(CopyState cstate)
Assert(resultRelInfo != NULL);
}
! /* 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
--- 2615,2639 ----
Assert(resultRelInfo != NULL);
}
! /*
! * Verify the specified partition is a valid target for COPY if we
! * didn't yet.
! */
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! /* We do not yet have a way to copy into a foreign partition */
! if (resultRelInfo->ri_FdwRoutine)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route copied tuples to a foreign table")));
! /* OK, do the remaining initilization work. */
! ExecInitPartitionExtraInfo(NULL, estate,
! proute,
! resultRelInfo,
! leaf_part_index);
!
! resultRelInfo->ri_PartitionIsValid = true;
! }
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
***************
*** 2813,2819 **** CopyFrom(CopyState cstate)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
--- 2827,2833 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(NULL, cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1172,1189 **** 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,
--- 1172,1193 ----
switch (operation)
{
case CMD_INSERT:
if (resultRelInfo->ri_PartitionRoot)
! {
! if (fdwroutine->ExecForeignRouting == NULL)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to foreign table \"%s\"",
! RelationGetRelationName(resultRel))));
! }
! else
! {
! 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,
***************
*** 1364,1369 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1368,1374 ----
resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionIsValid = false;
}
/*
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 17,22 ****
--- 17,23 ----
#include "catalog/pg_inherits_fn.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/lsyscache.h"
***************
*** 56,62 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
- TupleDesc tupDesc = RelationGetDescr(rel);
List *leaf_parts;
ListCell *cell;
int i;
--- 57,62 ----
***************
*** 128,138 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
if (update_rri_index < num_update_rri &&
RelationGetRelid(update_rri[update_rri_index].ri_RelationDesc) == leaf_oid)
{
- Relation partrel;
- TupleDesc part_tupdesc;
-
leaf_part_rri = &update_rri[update_rri_index];
- partrel = leaf_part_rri->ri_RelationDesc;
/*
* This is required in order to convert the partition's tuple to
--- 128,134 ----
***************
*** 146,168 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
proute->subplan_partition_offsets[update_rri_index] = i;
update_rri_index++;
-
- 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.
- */
- proute->parent_child_tupconv_maps[i] =
- convert_tuples_by_name(tupDesc, part_tupdesc,
- gettext_noop("could not convert row type"));
-
- /*
- * Verify result relation is a valid target for an INSERT. An
- * UPDATE of a partition-key becomes a DELETE+INSERT operation, so
- * this check is required even when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
}
proute->partitions[i] = leaf_part_rri;
--- 142,147 ----
***************
*** 336,348 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
estate->es_instrument);
/*
- * Verify result relation is a valid target for an INSERT. An UPDATE of a
- * partition-key becomes a DELETE+INSERT operation, so this check is still
- * required when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
-
- /*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
*
--- 315,320 ----
***************
*** 459,464 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 431,437 ----
returningList = map_partition_varattnos(returningList, firstVarno,
partrel, firstResultRel,
NULL);
+ leaf_part_rri->ri_returningList = returningList;
/*
* Initialize the projection itself.
***************
*** 478,495 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
Assert(proute->partitions[partidx] == NULL);
proute->partitions[partidx] = leaf_part_rri;
/*
! * Save a tuple conversion map to convert a tuple routed to this partition
! * from the parent's type to the partition's.
*/
proute->parent_child_tupconv_maps[partidx] =
! convert_tuples_by_name(RelationGetDescr(rootrel),
! RelationGetDescr(partrel),
gettext_noop("could not convert row type"));
! MemoryContextSwitchTo(oldContext);
! return leaf_part_rri;
}
/*
--- 451,500 ----
Assert(proute->partitions[partidx] == NULL);
proute->partitions[partidx] = leaf_part_rri;
+ MemoryContextSwitchTo(oldContext);
+
+ return leaf_part_rri;
+ }
+
+ /*
+ * ExecInitPartitionExtraInfo
+ * Do the remaining initialization work for the given partition
+ */
+ void
+ ExecInitPartitionExtraInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *resultRelInfo,
+ int partidx)
+ {
+ MemoryContext oldContext;
+
+ /*
+ * Switch into per-query memory context.
+ */
+ oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+
/*
! * Set up a tuple conversion map to convert a tuple routed to the given
! * partition from the parent's type to the partition's.
*/
proute->parent_child_tupconv_maps[partidx] =
! convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_PartitionRoot),
! RelationGetDescr(resultRelInfo->ri_RelationDesc),
gettext_noop("could not convert row type"));
! /*
! * If this is INSERT/UPDATE and the given partition is a foreign table,
! * let the FDW init itself for the partition.
! */
! if (mtstate &&
! resultRelInfo->ri_FdwRoutine != NULL &&
! resultRelInfo->ri_FdwRoutine->BeginForeignRouting != NULL)
! resultRelInfo->ri_FdwRoutine->BeginForeignRouting(mtstate,
! resultRelInfo,
! partidx);
! MemoryContextSwitchTo(oldContext);
}
/*
***************
*** 594,600 **** ConvertPartitionTupleSlot(TupleConversionMap *map,
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
--- 599,606 ----
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(ModifyTableState *node,
! PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
***************
*** 623,628 **** ExecCleanupTupleRouting(PartitionTupleRouting *proute)
--- 629,643 ----
continue;
/*
+ * If this is INSERT/UPDATE, allow any FDWs to shut down
+ */
+ if (node &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignRouting != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignRouting(node->ps.state,
+ resultRelInfo);
+
+ /*
* If this result rel is one of the UPDATE subplan result rels, let
* ExecEndPlan() close it. For INSERT or COPY,
* proute->subplan_partition_offsets will always be NULL. Note that
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 319,329 **** ExecInsert(ModifyTableState *mtstate,
Assert(resultRelInfo != NULL);
}
! /* 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;
--- 319,340 ----
Assert(resultRelInfo != NULL);
}
! /*
! * Verify the specified partition is a valid target for INSERT if we
! * didn't yet.
! */
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! CheckValidResultRel(resultRelInfo, CMD_INSERT);
!
! /* OK, do the remaining initilization work. */
! ExecInitPartitionExtraInfo(mtstate, estate,
! proute,
! resultRelInfo,
! leaf_part_index);
!
! resultRelInfo->ri_PartitionIsValid = true;
! }
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
***************
*** 433,442 **** ExecInsert(ModifyTableState *mtstate,
/*
* insert into foreign table: let the FDW do it
*/
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! planSlot);
if (slot == NULL) /* "do nothing" */
return NULL;
--- 444,458 ----
/*
* insert into foreign table: let the FDW do it
*/
! if (resultRelInfo->ri_PartitionRoot)
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignRouting(estate,
! resultRelInfo,
! slot);
! else
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! planSlot);
if (slot == NULL) /* "do nothing" */
return NULL;
***************
*** 2310,2315 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 2326,2332 ----
{
List *rlist = (List *) lfirst(l);
+ resultRelInfo->ri_returningList = rlist;
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
***************
*** 2568,2574 **** ExecEndModifyTable(ModifyTableState *node)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node->mt_partition_tuple_routing);
/*
* Free the exprcontext
--- 2585,2591 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
/*
* Free the exprcontext
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 118,123 **** extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 118,128 ----
ResultRelInfo *resultRelInfo,
PartitionTupleRouting *proute,
EState *estate, int partidx);
+ extern void ExecInitPartitionExtraInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx);
extern void ExecSetupChildParentMapForLeaf(PartitionTupleRouting *proute);
extern TupleConversionMap *TupConvMapForLeaf(PartitionTupleRouting *proute,
ResultRelInfo *rootRelInfo, int leaf_index);
***************
*** 125,130 **** extern HeapTuple ConvertPartitionTupleSlot(TupleConversionMap *map,
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
--- 130,136 ----
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(ModifyTableState *node,
! PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 97,102 **** typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
--- 97,113 ----
typedef void (*EndForeignModify_function) (EState *estate,
ResultRelInfo *rinfo);
+ typedef void (*BeginForeignRouting_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo,
+ int partition_index);
+
+ typedef TupleTableSlot *(*ExecForeignRouting_function) (EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot);
+
+ typedef void (*EndForeignRouting_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
typedef int (*IsForeignRelUpdatable_function) (Relation rel);
typedef bool (*PlanDirectModify_function) (PlannerInfo *root,
***************
*** 204,209 **** typedef struct FdwRoutine
--- 215,223 ----
ExecForeignUpdate_function ExecForeignUpdate;
ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify;
+ BeginForeignRouting_function BeginForeignRouting;
+ ExecForeignRouting_function ExecForeignRouting;
+ EndForeignRouting_function EndForeignRouting;
IsForeignRelUpdatable_function IsForeignRelUpdatable;
PlanDirectModify_function PlanDirectModify;
BeginDirectModify_function BeginDirectModify;
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 403,408 **** typedef struct ResultRelInfo
--- 403,411 ----
/* list of WithCheckOption expr states */
List *ri_WithCheckOptionExprs;
+ /* list of RETURNING expressions */
+ List *ri_returningList;
+
/* array of constraint-checking expr states */
ExprState **ri_ConstraintExprs;
***************
*** 426,431 **** typedef struct ResultRelInfo
--- 429,437 ----
/* relation descriptor for root partitioned table */
Relation ri_PartitionRoot;
+
+ /* true if valid target for tuple routing */
+ bool ri_PartitionIsValid;
} ResultRelInfo;
/* ----------------
Fujita-san,
Thanks for sending the updated patches.
On 2018/02/27 21:01, Etsuro Fujita wrote:
(2018/02/21 20:54), Etsuro Fujita wrote:
void
BeginForeignRouting();Prepare for a tuple-routing operation on a foreign table. This is called
from ExecSetupPartitionTupleRouting and ExecInitPartitionInfo.I modified execPartition.c so that this callback routine is called from a
single function that I added to execPartition.c and it is called the first
time the foreign partition is chose as the target partition to route a
tuple to.� That removes CheckValidResultRel, the tuple-conversion setup,
and the FDW initialization for each UPDATE subplan from
ExecSetupPartitionTupleRouting, so it would minimize the possibly-useless
overhead in doing that function.Changes other than that are:
* Fixed typo and revised code/comments
* Added more regression tests
* Added docsAttached is a new version of the patch set.
Patches still seem to apply cleanly to HEAD, compile fine, tests pass.
* Comments postgres-fdw-refactoring-1.patch:
1. Just a minor nitpick: wouldn't it be better to call it
create_foreign_modify_state just like its "finish" counterpart that's
named finish_foreign_modify?
Other than that, it looks good to me.
* Comments on foreign-routing-fdwapi-1.patch:
1. In the following sentence, s/rows/a tuple/g, to be consistent with
other text added by the patch
+ <para>
+ If the <function>ExecForeignRouting</function> pointer is set to
+ <literal>NULL</literal>, attempts to route rows to the foreign table
will fail
+ with an error message.
+ </para>
2. If I understand the description you provided in [1]/messages/by-id/5A95487E.9050808@lab.ntt.co.jp correctly, the
point of having ri_PartitionIsValid and ExecInitExtraPartitionInfo() is to
avoid possibly-redundantly performing following two steps in
ExecSetupPartitionTupleRouting() for *reused* partition ResultRelInfos
that may not be used for tuple routing after all:
- create the parent_child_tupconv_maps[] entry for it
- perform FDW tuple routing initialization.
If that's true, the following comment could be expanded just a little bit
to make that clearer:
/*
* ExecInitPartitionExtraInfo
* Do the remaining initialization work for the given partition
3. You removed the following from ExecInitPartitionInfo:
/*
- * Verify result relation is a valid target for an INSERT. An UPDATE
of a
- * partition-key becomes a DELETE+INSERT operation, so this check is
still
- * required when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
but, only added back the following in ExecInsert:
+ /*
+ * Verify the specified partition is a valid target for INSERT if we
+ * didn't yet.
+ */
+ if (!resultRelInfo->ri_PartitionIsValid)
+ {
+ CheckValidResultRel(resultRelInfo, CMD_INSERT);
Maybe, the new comment should add a "Note: " line the comment saying
something about this code being invoked as part of an UPDATE.
Tests and documentation added by this patch look quite good.
That's all I have for now.
Thanks,
Amit
On 2018/03/19 20:25, Amit Langote wrote:
That's all I have for now.
While testing this patch, I noticed a crash when performing EXPLAIN on
update of a partition tree containing foreign partitions. Crash occurs in
postgresEndForeignRouting() due to the following Assert failing:
Assert(fmstate != NULL);
It seems the problem is that ExecCleanupTupleRouting() invokes the
EndForeignRouting() function even if ri_PartitionIsValid is not set. So I
suppose we need this:
/*
- * If this is INSERT/UPDATE, allow any FDWs to shut down
+ * If this is INSERT/UPDATE, allow any FDWs to shut down if it has
+ * initialized tuple routing information at all.
*/
if (node &&
+ resultRelInfo->ri_PartitionIsValid &&
resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->EndForeignRouting != NULL)
resultRelInfo->ri_FdwRoutine->EndForeignRouting(node->ps.state,
BTW,patch needs to be rebased because of two commits this morning:
6666ee49f [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=6666ee49f and ee0a1fc84 [2]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=ee0a1fc84.
Thanks,
Amit
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=6666ee49f
[2]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=ee0a1fc84
Hi Amit,
(2018/03/20 15:57), Amit Langote wrote:
On 2018/03/19 20:25, Amit Langote wrote:
That's all I have for now.
Will reply to your previous email.
While testing this patch, I noticed a crash when performing EXPLAIN on
update of a partition tree containing foreign partitions. Crash occurs in
postgresEndForeignRouting() due to the following Assert failing:Assert(fmstate != NULL);
It seems the problem is that ExecCleanupTupleRouting() invokes the
EndForeignRouting() function even if ri_PartitionIsValid is not set. So I
suppose we need this:/* - * If this is INSERT/UPDATE, allow any FDWs to shut down + * If this is INSERT/UPDATE, allow any FDWs to shut down if it has + * initialized tuple routing information at all. */ if (node&& + resultRelInfo->ri_PartitionIsValid&& resultRelInfo->ri_FdwRoutine != NULL&& resultRelInfo->ri_FdwRoutine->EndForeignRouting != NULL) resultRelInfo->ri_FdwRoutine->EndForeignRouting(node->ps.state,
Will look into this.
BTW,patch needs to be rebased because of two commits this morning:
6666ee49f [1] and ee0a1fc84 [2].
Will do.
Thanks for reviewing the patch!
Best regards,
Etsuro Fujita
Hi,
Amit Langote wrote:
2. If I understand the description you provided in [1] correctly, the
point of having ri_PartitionIsValid and ExecInitExtraPartitionInfo() is to
avoid possibly-redundantly performing following two steps in
ExecSetupPartitionTupleRouting() for *reused* partition ResultRelInfos
that may not be used for tuple routing after all:- create the parent_child_tupconv_maps[] entry for it
- perform FDW tuple routing initialization.If that's true, the following comment could be expanded just a little bit
to make that clearer:/*
* ExecInitPartitionExtraInfo
* Do the remaining initialization work for the given partition
Yeah, I think the comments on why this is a separate step should be more
extensive. Probably add something to ExecInitPartitionInfo too.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Feb 27, 2018 at 7:01 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
Changes other than that are:
* Fixed typo and revised code/comments
* Added more regression tests
* Added docsAttached is a new version of the patch set.
I took a look at this patch set today but I really don't think we
should commit something so minimal. It's got at least four issues
that I see:
1. It still doesn't work for COPY, because COPY isn't going to have a
ModifyTableState. I really think it ought to be possible to come up
with an API that can handle both INSERT and COPY; I don't think it
should be necessary to have two different APIs for those two problems.
Amit managed to do it for regular tables, and I don't really see a
good reason why foreign tables need to be different.
2. I previously asked why we couldn't use the existing APIs for this,
and you gave me some answer, but I can't help noticing that
postgresExecForeignRouting is an exact copy of
postgresExecForeignInsert. Apparently, some code reuse is indeed
possible here! Why not reuse the same function instead of calling a
new one? If the answer is that planSlot might be NULL in this case,
or something like that, then let's just document that. A needless
proliferation of FDW APIs is really undesirable, as it raises the bar
for every FDW author.
3. It looks like we're just doing an INSERT for every row, which is
pretty much an anti-pattern for inserting data into a PostgreSQL
database. COPY is way faster, and even multi-row inserts are
significantly faster.
4. It doesn't do anything about the UPDATE tuple routing problem
mentioned upthread.
I don't necessarily think that the first patch in this area has to
solve all of those problems, and #4 in particular seems like it might
be a pretty big can of worms. However, I think that getting INSERT
but not COPY, with bad performance, and with duplicated APIs, is
moving more in the wrong direction than the right one.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
(2018/03/23 4:09), Robert Haas wrote:
On Tue, Feb 27, 2018 at 7:01 AM, Etsuro Fujita
Attached is a new version of the patch set.
I took a look at this patch set today but I really don't think we
should commit something so minimal. It's got at least four issues
that I see:1. It still doesn't work for COPY, because COPY isn't going to have a
ModifyTableState. I really think it ought to be possible to come up
with an API that can handle both INSERT and COPY; I don't think it
should be necessary to have two different APIs for those two problems.
Amit managed to do it for regular tables, and I don't really see a
good reason why foreign tables need to be different.
Maybe I'm missing something, but I think the proposed FDW API could be
used for the COPY case as well with some modifications to core. If so,
my question is: should we support COPY into foreign tables as well? I
think that if we support COPY tuple routing for foreign partitions, it
would be better to support direct COPY into foreign partitions as well.
2. I previously asked why we couldn't use the existing APIs for this,
and you gave me some answer, but I can't help noticing that
postgresExecForeignRouting is an exact copy of
postgresExecForeignInsert. Apparently, some code reuse is indeed
possible here! Why not reuse the same function instead of calling a
new one? If the answer is that planSlot might be NULL in this case,
or something like that, then let's just document that. A needless
proliferation of FDW APIs is really undesirable, as it raises the bar
for every FDW author.
You've got a point! I'll change the patch that way.
3. It looks like we're just doing an INSERT for every row, which is
pretty much an anti-pattern for inserting data into a PostgreSQL
database. COPY is way faster, and even multi-row inserts are
significantly faster.
I planed to work on new FDW API for using COPY for COPY tuple routing
[1]: /messages/by-id/23990375-45a6-5823-b0aa-a6a7a6a957f0@lab.ntt.co.jp
re-planning to work on that for PG12. I'm not sure we can optimize that
insertion using multi-row inserts because tuple routing works row by
row, as you know. Anyway, I think these would be beyond the scope of
the first version.
4. It doesn't do anything about the UPDATE tuple routing problem
mentioned upthread.I don't necessarily think that the first patch in this area has to
solve all of those problems, and #4 in particular seems like it might
be a pretty big can of worms.
OK
However, I think that getting INSERT
but not COPY, with bad performance, and with duplicated APIs, is
moving more in the wrong direction than the right one.
Will fix.
Thanks for reviewing the patch!
Best regards,
Etsuro Fujita
[1]: /messages/by-id/23990375-45a6-5823-b0aa-a6a7a6a957f0@lab.ntt.co.jp
/messages/by-id/23990375-45a6-5823-b0aa-a6a7a6a957f0@lab.ntt.co.jp
On Fri, Mar 23, 2018 at 7:55 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
Maybe I'm missing something, but I think the proposed FDW API could be used
for the COPY case as well with some modifications to core. If so, my
question is: should we support COPY into foreign tables as well? I think
that if we support COPY tuple routing for foreign partitions, it would be
better to support direct COPY into foreign partitions as well.
Yes, I really, really want to be able to support COPY. If you think
you can make that work with this API -- let's see it!
3. It looks like we're just doing an INSERT for every row, which is
pretty much an anti-pattern for inserting data into a PostgreSQL
database. COPY is way faster, and even multi-row inserts are
significantly faster.I planed to work on new FDW API for using COPY for COPY tuple routing [1],
but I didn't have time for that in this development cycle, so I'm
re-planning to work on that for PG12. I'm not sure we can optimize that
insertion using multi-row inserts because tuple routing works row by row, as
you know. Anyway, I think these would be beyond the scope of the first
version.
My concern is that if we add APIs now that only support single-row
inserts, we'll have to rework them again in order to support multi-row
inserts. I'd like to avoid that, if possible. I think for bulk
inserts we'll need an API that says "here's a row, store it or buffer
it as you like" and then another API that says "flush any buffered
rows to the actual table and perform any necessary cleanup". Or maybe
in postgres_fdw the first API could start a COPY if not already done
and send the row, and the second one could end the COPY.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
(2018/03/23 21:02), Robert Haas wrote:
On Fri, Mar 23, 2018 at 7:55 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:Maybe I'm missing something, but I think the proposed FDW API could be used
for the COPY case as well with some modifications to core. If so, my
question is: should we support COPY into foreign tables as well? I think
that if we support COPY tuple routing for foreign partitions, it would be
better to support direct COPY into foreign partitions as well.Yes, I really, really want to be able to support COPY. If you think
you can make that work with this API -- let's see it!
OK
3. It looks like we're just doing an INSERT for every row, which is
pretty much an anti-pattern for inserting data into a PostgreSQL
database. COPY is way faster, and even multi-row inserts are
significantly faster.I planed to work on new FDW API for using COPY for COPY tuple routing [1],
but I didn't have time for that in this development cycle, so I'm
re-planning to work on that for PG12. I'm not sure we can optimize that
insertion using multi-row inserts because tuple routing works row by row, as
you know. Anyway, I think these would be beyond the scope of the first
version.My concern is that if we add APIs now that only support single-row
inserts, we'll have to rework them again in order to support multi-row
inserts. I'd like to avoid that, if possible.
Yeah, but we would have this issue for normal inserts into foreign
tables. For the normal case, what I'm thinking to support multi-row
inserts is to use DirectModify FDW API. With this API, I think we could
support pushing down INSERT with multiple VALUES sublists to the remote,
but I'm not sure we could extend that to the tuple-routing case.
I think for bulk
inserts we'll need an API that says "here's a row, store it or buffer
it as you like" and then another API that says "flush any buffered
rows to the actual table and perform any necessary cleanup". Or maybe
in postgres_fdw the first API could start a COPY if not already done
and send the row, and the second one could end the COPY.
That's really what I have in mind.
Best regards,
Etsuro Fujita
On Fri, Mar 23, 2018 at 8:22 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
I think for bulk
inserts we'll need an API that says "here's a row, store it or buffer
it as you like" and then another API that says "flush any buffered
rows to the actual table and perform any necessary cleanup". Or maybe
in postgres_fdw the first API could start a COPY if not already done
and send the row, and the second one could end the COPY.That's really what I have in mind.
So let's do it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
(2018/03/23 20:55), Etsuro Fujita wrote:
(2018/03/23 4:09), Robert Haas wrote:
1. It still doesn't work for COPY, because COPY isn't going to have a
ModifyTableState. I really think it ought to be possible to come up
with an API that can handle both INSERT and COPY; I don't think it
should be necessary to have two different APIs for those two problems.
Amit managed to do it for regular tables, and I don't really see a
good reason why foreign tables need to be different.Maybe I'm missing something, but I think the proposed FDW API could be
used for the COPY case as well with some modifications to core. If so,
my question is: should we support COPY into foreign tables as well? I
think that if we support COPY tuple routing for foreign partitions, it
would be better to support direct COPY into foreign partitions as well.
Done.
2. I previously asked why we couldn't use the existing APIs for this,
and you gave me some answer, but I can't help noticing that
postgresExecForeignRouting is an exact copy of
postgresExecForeignInsert. Apparently, some code reuse is indeed
possible here! Why not reuse the same function instead of calling a
new one? If the answer is that planSlot might be NULL in this case,
or something like that, then let's just document that. A needless
proliferation of FDW APIs is really undesirable, as it raises the bar
for every FDW author.You've got a point! I'll change the patch that way.
Done. I modified the patch so that the existing API
postgresExecForeignInsert is called as-is (ie, with the planSlot
parameter) in the INSERT case and is called with that parameter set to
NULL in the COPY case. So, I removed postgresExecForeignRouting and the
postgres_fdw refactoring for that API. Also, I changed the names of the
remaining new APIs to postgresBeginForeignInsert and
postgresEndForeignInsert, which I think would be better because these
are used not only for tuple routing but for directly copying into
foreign tables. Also, I dropped partition_index from the parameter list
for postgresBeginForeignInsert; I thought that it could be used for the
FDW to access the partition info stored in mt_partition_tuple_routing
for something in the case of tuple-routing, but I started to think that
the FDW wouldn't need that in practice.
However, I think that getting INSERT
but not COPY, with bad performance, and with duplicated APIs, is
moving more in the wrong direction than the right one.Will fix.
Done.
Attached is the new version of the patch. Patch
foreign-routing-fdwapi-2.patch is created on top of patch
postgres-fdw-refactoring-2.patch. (The former contains the bug-fix
[1]: /messages/by-id/5ABA4074.1090500@lab.ntt.co.jp
Best regards,
Etsuro Fujita
Attachments:
postgres-fdw-refactoring-2.patchtext/x-diff; name=postgres-fdw-refactoring-2.patchDownload
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 375,386 **** static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
--- 375,395 ----
static void create_cursor(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
+ static PgFdwModifyState *create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs);
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
+ static void finish_foreign_modify(PgFdwModifyState *fmstate);
static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
***************
*** 1678,1695 **** postgresBeginForeignModify(ModifyTableState *mtstate,
int eflags)
{
PgFdwModifyState *fmstate;
! EState *estate = mtstate->ps.state;
! CmdType operation = mtstate->operation;
! Relation rel = resultRelInfo->ri_RelationDesc;
! RangeTblEntry *rte;
! Oid userid;
! ForeignTable *table;
! UserMapping *user;
! AttrNumber n_params;
! Oid typefnoid;
! bool isvarlena;
! ListCell *lc;
! TupleDesc tupdesc = RelationGetDescr(rel);
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
--- 1687,1696 ----
int eflags)
{
PgFdwModifyState *fmstate;
! char *query;
! List *target_attrs;
! bool has_returning;
! List *retrieved_attrs;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
***************
*** 1698,1779 **** postgresBeginForeignModify(ModifyTableState *mtstate,
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
- /* Begin constructing PgFdwModifyState. */
- fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
- fmstate->rel = rel;
-
- /*
- * Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
- */
- rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
- userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
- /* Get info about foreign table. */
- table = GetForeignTable(RelationGetRelid(rel));
- user = GetUserMapping(userid, table->serverid);
-
- /* Open connection; report that we'll create a prepared statement. */
- fmstate->conn = GetConnection(user, true);
- fmstate->p_name = NULL; /* prepared statement not made yet */
-
/* Deconstruct fdw_private data. */
! fmstate->query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! fmstate->target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! fmstate->has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! fmstate->retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
!
! /* Create context for per-tuple temp workspace. */
! fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
! "postgres_fdw temporary data",
! ALLOCSET_SMALL_SIZES);
!
! /* Prepare for input conversion of RETURNING results. */
! if (fmstate->has_returning)
! fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
!
! /* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 1;
! fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
! fmstate->p_nums = 0;
!
! if (operation == CMD_UPDATE || operation == CMD_DELETE)
! {
! /* Find the ctid resjunk column in the subplan's result */
! Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
!
! fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
! "ctid");
! if (!AttributeNumberIsValid(fmstate->ctidAttno))
! elog(ERROR, "could not find junk ctid column");
! /* First transmittable parameter will be ctid */
! getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
!
! if (operation == CMD_INSERT || operation == CMD_UPDATE)
! {
! /* Set up for remaining transmittable parameters */
! foreach(lc, fmstate->target_attrs)
! {
! int attnum = lfirst_int(lc);
! Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
!
! Assert(!attr->attisdropped);
!
! getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
! }
!
! Assert(fmstate->p_nums <= n_params);
resultRelInfo->ri_FdwState = fmstate;
}
--- 1699,1723 ----
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/* Deconstruct fdw_private data. */
! query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
! /* Construct an execution state. */
! fmstate = create_foreign_modify(mtstate->ps.state,
! resultRelInfo,
! mtstate->operation,
! mtstate->mt_plans[subplan_index]->plan,
! query,
! target_attrs,
! has_returning,
! retrieved_attrs);
resultRelInfo->ri_FdwState = fmstate;
}
***************
*** 2008,2035 **** postgresEndForeignModify(EState *estate,
if (fmstate == NULL)
return;
! /* If we created a prepared statement, destroy it */
! if (fmstate->p_name)
! {
! char sql[64];
! PGresult *res;
!
! snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
!
! /*
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_exec_query(fmstate->conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
! PQclear(res);
! fmstate->p_name = NULL;
! }
!
! /* Release remote connection */
! ReleaseConnection(fmstate->conn);
! fmstate->conn = NULL;
}
/*
--- 1952,1959 ----
if (fmstate == NULL)
return;
! /* Destroy the execution state. */
! finish_foreign_modify(fmstate);
}
/*
***************
*** 3217,3222 **** close_cursor(PGconn *conn, unsigned int cursor_number)
--- 3141,3249 ----
}
/*
+ * create_foreign_modify
+ * Construct an execution state of a foreign insert/update/delete
+ * operation.
+ */
+ static PgFdwModifyState *
+ create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs)
+ {
+ PgFdwModifyState *fmstate;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Oid userid;
+ ForeignTable *table;
+ UserMapping *user;
+ AttrNumber n_params;
+ Oid typefnoid;
+ bool isvarlena;
+ ListCell *lc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+
+ /* Begin constructing PgFdwModifyState. */
+ fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
+ fmstate->rel = rel;
+
+ /*
+ * Identify which user to do the remote access as. This should match what
+ * ExecCheckRTEPerms() does.
+ */
+ rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ /* Get info about foreign table. */
+ table = GetForeignTable(RelationGetRelid(rel));
+ user = GetUserMapping(userid, table->serverid);
+
+ /* Open connection; report that we'll create a prepared statement. */
+ fmstate->conn = GetConnection(user, true);
+ fmstate->p_name = NULL; /* prepared statement not made yet */
+
+ /* Set up remote query information. */
+ fmstate->query = query;
+ fmstate->target_attrs = target_attrs;
+ fmstate->has_returning = has_returning;
+ fmstate->retrieved_attrs = retrieved_attrs;
+
+ /* Create context for per-tuple temp workspace. */
+ fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+ "postgres_fdw temporary data",
+ ALLOCSET_SMALL_SIZES);
+
+ /* Prepare for input conversion of RETURNING results. */
+ if (fmstate->has_returning)
+ fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ /* Prepare for output conversion of parameters used in prepared stmt. */
+ n_params = list_length(fmstate->target_attrs) + 1;
+ fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
+ fmstate->p_nums = 0;
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ Assert(subplan != NULL);
+
+ /* Find the ctid resjunk column in the subplan's result */
+ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "ctid");
+ if (!AttributeNumberIsValid(fmstate->ctidAttno))
+ elog(ERROR, "could not find junk ctid column");
+
+ /* First transmittable parameter will be ctid */
+ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ /* Set up for remaining transmittable parameters */
+ foreach(lc, fmstate->target_attrs)
+ {
+ int attnum = lfirst_int(lc);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ Assert(!attr->attisdropped);
+
+ getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+ }
+
+ Assert(fmstate->p_nums <= n_params);
+
+ return fmstate;
+ }
+
+ /*
* prepare_foreign_modify
* Establish a prepared statement for execution of INSERT/UPDATE/DELETE
*/
***************
*** 3359,3364 **** store_returning_result(PgFdwModifyState *fmstate,
--- 3386,3424 ----
}
/*
+ * finish_foreign_modify
+ * Release resources for a foreign insert/update/delete operation.
+ */
+ static void
+ finish_foreign_modify(PgFdwModifyState *fmstate)
+ {
+ Assert(fmstate != NULL);
+
+ /* If we created a prepared statement, destroy it */
+ if (fmstate->p_name)
+ {
+ char sql[64];
+ PGresult *res;
+
+ snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
+
+ /*
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_exec_query(fmstate->conn, sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
+ PQclear(res);
+ fmstate->p_name = NULL;
+ }
+
+ /* Release remote connection */
+ ReleaseConnection(fmstate->conn);
+ fmstate->conn = NULL;
+ }
+
+ /*
* build_remote_returning
* Build a RETURNING targetlist of a remote query for performing an
* UPDATE/DELETE .. RETURNING on a join directly
foreign-routing-fdwapi-2.patchtext/x-diff; name=foreign-routing-fdwapi-2.patchDownload
*** 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 insert into foreign table "p1"
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
***************
*** 342,351 **** SELECT tableoid::regclass, * FROM p2;
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
--- 342,351 ----
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot insert into foreign table "p1"
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot insert into foreign table "p1"
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7371,7376 **** NOTICE: drop cascades to foreign table bar2
--- 7371,7700 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ -- Test insert tuple routing
+ create table itrtest (a int, b int, c text) partition by list (a);
+ create table loctab1 (a int check (a in (1)), b int primary key, c text);
+ create foreign table remp1 (a int check (a in (1)), b int, c text) server loopback options (table_name 'loctab1');
+ create table loctab2 (b int primary key, c text, a int check (a in (2)));
+ create foreign table remp2 (b int, c text, a int check (a in (2))) server loopback options (table_name 'loctab2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+ insert into itrtest values (1, 1, 'foo');
+ insert into itrtest values (1, 2, 'bar') returning *;
+ a | b | c
+ ---+---+-----
+ 1 | 2 | bar
+ (1 row)
+
+ insert into itrtest values (2, 1, 'baz') returning *;
+ a | b | c
+ ---+---+-----
+ 2 | 1 | baz
+ (1 row)
+
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ remp2 | 2 | 1 | baz
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | c | a
+ ----------+---+-----+---
+ remp2 | 1 | baz | 2
+ (1 row)
+
+ insert into itrtest values (2, 1, 'baz');
+ ERROR: duplicate key value violates unique constraint "loctab2_pkey"
+ DETAIL: Key (b)=(1) already exists.
+ CONTEXT: remote SQL command: INSERT INTO public.loctab2(b, c, a) VALUES ($1, $2, $3)
+ insert into itrtest values (2, 1, 'baz') on conflict do nothing;
+ insert into itrtest values (2, 2, 'qux') on conflict do nothing returning *;
+ a | b | c
+ ---+---+-----
+ 2 | 2 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ remp2 | 2 | 1 | baz
+ remp2 | 2 | 2 | qux
+ (4 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | c | a
+ ----------+---+-----+---
+ remp2 | 1 | baz | 2
+ remp2 | 2 | qux | 2
+ (2 rows)
+
+ drop table itrtest;
+ drop table loctab1;
+ drop table loctab2;
+ -- Test update tuple routing
+ create table utrtest (a int, b int, c text) partition by list (a);
+ create table loctab (a int check (a in (1)), b int, c text);
+ create foreign table remp (a int check (a in (1)), b int, c text) server loopback options (table_name 'loctab');
+ create table locp (a int check (a in (2)), b int, c text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+ insert into utrtest values (1, 1, 'foo');
+ insert into utrtest values (2, 2, 'qux');
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp | 1 | 1 | foo
+ locp | 2 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp | 1 | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ locp | 2 | 2 | qux
+ (1 row)
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where c = 'foo' returning *;
+ ERROR: new row for relation "loctab" violates check constraint "loctab_a_check"
+ DETAIL: Failing row contains (2, 1, foo).
+ CONTEXT: remote SQL command: UPDATE public.loctab SET a = 2 WHERE ((c = 'foo'::text)) RETURNING a, b, c
+ -- But the reverse is allowed
+ update utrtest set a = 1 where c = 'qux' returning *;
+ a | b | c
+ ---+---+-----
+ 1 | 2 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp | 1 | 1 | foo
+ remp | 1 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp | 1 | 1 | foo
+ remp | 1 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b | c
+ ----------+---+---+---
+ (0 rows)
+
+ -- Test that the executor doesn't let unexercised FDWs shut down
+ update utrtest set a = 1 where c = 'foo';
+ drop table utrtest;
+ drop table loctab;
+ -- Test copy tuple routing
+ create table ctrtest (a int, b int, c text) partition by list (a);
+ create table loctab1 (a int check (a in (1)), b int, c text);
+ create foreign table remp1 (a int check (a in (1)), b int, c text) server loopback options (table_name 'loctab1');
+ create table loctab2 (b int, c text, a int check (a in (2)));
+ create foreign table remp2 (b int, c text, a int check (a in (2))) server loopback options (table_name 'loctab2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+ copy ctrtest from stdin;
+ select tableoid::regclass, * FROM ctrtest;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp2 | 2 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | c | a
+ ----------+---+-----+---
+ remp2 | 2 | qux | 2
+ (1 row)
+
+ -- Copying into foreign partitions directly works as well
+ copy remp1 from stdin;
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b | c
+ ----------+---+---+-----
+ remp1 | 1 | 1 | foo
+ remp1 | 1 | 2 | bar
+ (2 rows)
+
+ drop table ctrtest;
+ drop table loctab1;
+ drop table loctab2;
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+ -- Test basic functionality
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ delete from rem2;
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+ copy rem2 from stdin; -- fail on remote side
+ ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive"
+ DETAIL: Failing row contains (-1, xyzzy).
+ CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2)
+ COPY rem2, line 1: "-1 xyzzy"
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ copy rem2 from stdin;
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+ delete from rem2;
+ create trigger trig_row_before_insupdate before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insupdate on rem2;
+ delete from rem2;
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on rem2;
+ delete from rem2;
+ -- Test remote triggers
+ create trigger trig_row_before_insupdate before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insupdate on loc2;
+ delete from rem2;
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on loc2;
+ delete from rem2;
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insupdate before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ copy rem2 from stdin;
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,"foo triggered !")
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,"bar triggered !")
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insupdate on loc2;
+ delete from rem2;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 319,324 **** static TupleTableSlot *postgresExecForeignDelete(EState *estate,
--- 319,328 ----
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
+ static void postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo);
+ static void postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo);
static int postgresIsForeignRelUpdatable(Relation rel);
static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
***************
*** 470,475 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 474,481 ----
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
+ routine->BeginForeignInsert = postgresBeginForeignInsert;
+ routine->EndForeignInsert = postgresEndForeignInsert;
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
routine->PlanDirectModify = postgresPlanDirectModify;
routine->BeginDirectModify = postgresBeginDirectModify;
***************
*** 1957,1962 **** postgresEndForeignModify(EState *estate,
--- 1963,2058 ----
}
/*
+ * postgresBeginForeignInsert
+ * Begin an insert operation on a foreign table
+ */
+ static void
+ postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate;
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Query *query;
+ PlannerInfo *root;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int attnum;
+ StringInfoData sql;
+ List *targetAttrs = NIL;
+ List *retrieved_attrs = NIL;
+ bool doNothing = false;
+
+ initStringInfo(&sql);
+
+ /* Set up largely-dummy planner state */
+ rte = makeNode(RangeTblEntry);
+ rte->rtekind = RTE_RELATION;
+ rte->relid = RelationGetRelid(rel);
+ rte->relkind = RELKIND_FOREIGN_TABLE;
+ query = makeNode(Query);
+ query->commandType = CMD_INSERT;
+ query->resultRelation = 1;
+ query->rtable = list_make1(rte);
+ root = makeNode(PlannerInfo);
+ root->parse = query;
+
+ /* We transmit all columns that are defined in the foreign table. */
+ for (attnum = 1; attnum <= tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (!attr->attisdropped)
+ targetAttrs = lappend_int(targetAttrs, attnum);
+ }
+
+ /* Check if we add the ON CONFLICT clause to the remote query. */
+ if (plan)
+ {
+ OnConflictAction onConflictAction = plan->onConflictAction;
+
+ /* We only support DO NOTHING without an inference specification. */
+ if (onConflictAction == ONCONFLICT_NOTHING)
+ doNothing = true;
+ else if (onConflictAction != ONCONFLICT_NONE)
+ elog(ERROR, "unexpected ON CONFLICT specification: %d",
+ (int) onConflictAction);
+ }
+
+ /* Construct the SQL command string. */
+ deparseInsertSql(&sql, root, 1, rel, targetAttrs, doNothing,
+ resultRelInfo->ri_returningList, &retrieved_attrs);
+
+ /* Construct an execution state. */
+ fmstate = create_foreign_modify(mtstate->ps.state,
+ resultRelInfo,
+ CMD_INSERT,
+ NULL,
+ sql.data,
+ targetAttrs,
+ retrieved_attrs != NIL,
+ retrieved_attrs);
+
+ resultRelInfo->ri_FdwState = fmstate;
+ }
+
+ /*
+ * postgresEndForeignInsert
+ * Finish an insert operation on a foreign table
+ */
+ static void
+ postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+
+ Assert(fmstate != NULL);
+
+ /* Destroy the execution state. */
+ finish_foreign_modify(fmstate);
+ }
+
+ /*
* postgresIsForeignRelUpdatable
* Determine whether a foreign table supports INSERT, UPDATE and/or
* DELETE.
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1768,1773 **** drop table loct1;
--- 1768,1995 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ -- Test insert tuple routing
+ create table itrtest (a int, b int, c text) partition by list (a);
+ create table loctab1 (a int check (a in (1)), b int primary key, c text);
+ create foreign table remp1 (a int check (a in (1)), b int, c text) server loopback options (table_name 'loctab1');
+ create table loctab2 (b int primary key, c text, a int check (a in (2)));
+ create foreign table remp2 (b int, c text, a int check (a in (2))) server loopback options (table_name 'loctab2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+
+ insert into itrtest values (1, 1, 'foo');
+ insert into itrtest values (1, 2, 'bar') returning *;
+ insert into itrtest values (2, 1, 'baz') returning *;
+
+ select tableoid::regclass, * FROM itrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ insert into itrtest values (2, 1, 'baz');
+ insert into itrtest values (2, 1, 'baz') on conflict do nothing;
+ insert into itrtest values (2, 2, 'qux') on conflict do nothing returning *;
+
+ select tableoid::regclass, * FROM itrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ drop table itrtest;
+ drop table loctab1;
+ drop table loctab2;
+
+ -- Test update tuple routing
+ create table utrtest (a int, b int, c text) partition by list (a);
+ create table loctab (a int check (a in (1)), b int, c text);
+ create foreign table remp (a int check (a in (1)), b int, c text) server loopback options (table_name 'loctab');
+ create table locp (a int check (a in (2)), b int, c text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+
+ insert into utrtest values (1, 1, 'foo');
+ insert into utrtest values (2, 2, 'qux');
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where c = 'foo' returning *;
+
+ -- But the reverse is allowed
+ update utrtest set a = 1 where c = 'qux' returning *;
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- Test that the executor doesn't let unexercised FDWs shut down
+ update utrtest set a = 1 where c = 'foo';
+
+ drop table utrtest;
+ drop table loctab;
+
+ -- Test copy tuple routing
+ create table ctrtest (a int, b int, c text) partition by list (a);
+ create table loctab1 (a int check (a in (1)), b int, c text);
+ create foreign table remp1 (a int check (a in (1)), b int, c text) server loopback options (table_name 'loctab1');
+ create table loctab2 (b int, c text, a int check (a in (2)));
+ create foreign table remp2 (b int, c text, a int check (a in (2))) server loopback options (table_name 'loctab2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+
+ copy ctrtest from stdin;
+ 1 1 foo
+ 2 2 qux
+ \.
+
+ select tableoid::regclass, * FROM ctrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ -- Copying into foreign partitions directly works as well
+ copy remp1 from stdin;
+ 1 2 bar
+ \.
+
+ select tableoid::regclass, * FROM remp1;
+
+ drop table ctrtest;
+ drop table loctab1;
+ drop table loctab2;
+
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+
+ -- Test basic functionality
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ delete from rem2;
+
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+
+ copy rem2 from stdin; -- fail on remote side
+ -1 xyzzy
+ \.
+ select * from rem2;
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+
+ delete from rem2;
+
+ create trigger trig_row_before_insupdate before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insupdate on rem2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on rem2;
+
+ delete from rem2;
+
+ -- Test remote triggers
+ create trigger trig_row_before_insupdate before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insupdate on loc2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on loc2;
+
+ delete from rem2;
+
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insupdate before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insupdate on loc2;
+
+ delete from rem2;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 3037,3047 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<para>
--- 3037,3045 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<para>
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 690,695 **** EndForeignModify(EState *estate,
--- 690,759 ----
</para>
<para>
+ Tuples inserted into a partitioned table are routed to partitions. If an
+ FDW supports routable foreign-table partitions, it should also provide
+ the following callback functions. These functions are also called when
+ <command>COPY FROM</command> is executed on a foreign table.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ BeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ Begin executing an insert operation on a foreign table. This routine is
+ called right before the first tuple is inserted into the foreign table
+ in both cases where it is the partition chosen for tuple routing and the
+ target specified in a <command>COPY FROM</command> command. It should
+ perform any initialization needed prior to the actual insertion.
+ Subsequently, <function>ExecForeignInsert</function> will be called for
+ each tuple to be inserted into the foreign table.
+ </para>
+
+ <para>
+ <literal>mtstate</literal> is the overall state of the
+ <structname>ModifyTable</structname> plan node being executed; global data about
+ the plan and execution state is available via this structure.
+ <literal>rinfo</literal> is the <structname>ResultRelInfo</structname> struct describing
+ the target foreign table. (The <structfield>ri_FdwState</structfield> field of
+ <structname>ResultRelInfo</structname> is available for the FDW to store any
+ private state it needs for this operation.)
+ </para>
+
+ <para>
+ When this is called by a <command>COPY FROM</command> command, the
+ plan-related global data in <literal>mtstate</literal> is not provided
+ and the <literal>planSlot</literal> parameter of
+ <function>ExecForeignInsert</function> called for each inserted tuple is
+ <literal>NULL</literal>, wether the foreign table is the partition chosen
+ for tuple routing or the target specified in the command.
+ </para>
+
+ <para>
+ If the <function>BeginForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the initialization.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ EndForeignInsert(EState *estate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ End the insert operation and release resources. It is normally not important
+ to release palloc'd memory, but for example open files and connections
+ to remote servers should be cleaned up.
+ </para>
+
+ <para>
+ If the <function>EndForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the termination.
+ </para>
+
+ <para>
<programlisting>
int
IsForeignRelUpdatable(Relation rel);
*** a/doc/src/sgml/ref/copy.sgml
--- b/doc/src/sgml/ref/copy.sgml
***************
*** 402,409 **** COPY <replaceable class="parameter">count</replaceable>
</para>
<para>
! <command>COPY FROM</command> can be used with plain tables and with views
! that have <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
--- 402,410 ----
</para>
<para>
! <command>COPY FROM</command> can be used with plain, foreign, or
! partitioned tables and with views that have
! <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 29,34 ****
--- 29,35 ----
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
***************
*** 2284,2289 **** CopyFrom(CopyState cstate)
--- 2285,2291 ----
ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
+ ModifyTableState *mtstate;
ExprContext *econtext;
TupleTableSlot *myslot;
MemoryContext oldcontext = CurrentMemoryContext;
***************
*** 2305,2315 **** CopyFrom(CopyState cstate)
Assert(cstate->rel);
/*
! * The target must be a plain relation or have an INSTEAD OF INSERT row
! * trigger. (Currently, such triggers are only allowed on views, so we
! * only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
--- 2307,2318 ----
Assert(cstate->rel);
/*
! * The target must be a plain, foreign, or partitioned relation, or have
! * an INSTEAD OF INSERT row trigger. (Currently, such triggers are only
! * allowed on views, so we only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
***************
*** 2325,2335 **** CopyFrom(CopyState cstate)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy to materialized view \"%s\"",
RelationGetRelationName(cstate->rel))));
- else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy to foreign table \"%s\"",
- RelationGetRelationName(cstate->rel))));
else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
--- 2328,2333 ----
***************
*** 2448,2453 **** CopyFrom(CopyState cstate)
--- 2446,2466 ----
/* Triggers might need a slot as well */
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+ /*
+ * Set up a ModifyTableState so we can let FDW(s) init themselves for
+ * foreign-table result relation(s).
+ */
+ mtstate = makeNode(ModifyTableState);
+ mtstate->ps.plan = NULL;
+ mtstate->ps.state = estate;
+ mtstate->operation = CMD_INSERT;
+ mtstate->resultRelInfo = estate->es_result_relations;
+
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate,
+ resultRelInfo);
+
/* Prepare to catch AFTER triggers. */
AfterTriggerBeginQuery();
***************
*** 2489,2499 **** CopyFrom(CopyState cstate)
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
--- 2502,2513 ----
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is foreign or partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ resultRelInfo->ri_FdwRoutine != NULL ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
***************
*** 2615,2625 **** CopyFrom(CopyState cstate)
Assert(resultRelInfo != NULL);
}
! /* 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
--- 2629,2647 ----
Assert(resultRelInfo != NULL);
}
! /*
! * Verify the specified partition is a valid target for INSERT if
! * we didn't yet.
! */
! if (!resultRelInfo->ri_PartitionIsValid)
! {
! CheckValidResultRel(resultRelInfo, CMD_INSERT);
!
! resultRelInfo->ri_PartitionIsValid = true;
!
! /* Let the FDW init itself for tuple routing. */
! ExecInitForeignRouting(mtstate, estate, resultRelInfo);
! }
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
***************
*** 2708,2715 **** CopyFrom(CopyState cstate)
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /* Check the constraints of the tuple */
! if (cstate->rel->rd_att->constr || check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
--- 2730,2742 ----
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /*
! * If the target is a plain table, check the constraints of
! * the tuple.
! */
! if (resultRelInfo->ri_FdwRoutine == NULL &&
! (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr))
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
***************
*** 2741,2750 **** CopyFrom(CopyState cstate)
{
List *recheckIndexes = NIL;
! /* OK, store the tuple and create index entries for it */
! heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
! hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
--- 2768,2799 ----
{
List *recheckIndexes = NIL;
! /* OK, store the tuple */
! if (resultRelInfo->ri_FdwRoutine != NULL)
! {
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! NULL);
!
! if (slot == NULL) /* "do nothing" */
! goto next_tuple;
!
! /* FDW might have changed tuple */
! tuple = ExecMaterializeSlot(slot);
+ /*
+ * AFTER ROW Triggers might reference the tableoid
+ * column, so initialize t_tableOid before evaluating
+ * them.
+ */
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+ else
+ heap_insert(resultRelInfo->ri_RelationDesc, tuple,
+ mycid, hi_options, bistate);
+
+ /* And create index entries for it */
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
***************
*** 2762,2774 **** CopyFrom(CopyState cstate)
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger;
! * this is the same definition used by execMain.c for counting
! * tuples inserted by an INSERT command.
*/
processed++;
}
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
--- 2811,2824 ----
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger
! * or FDW; this is the same definition used by nodeModifyTable.c
! * for counting tuples inserted by an INSERT command.
*/
processed++;
}
+ next_tuple:
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
***************
*** 2809,2819 **** CopyFrom(CopyState cstate)
ExecResetTupleTable(estate->es_tupleTable, false);
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
--- 2859,2875 ----
ExecResetTupleTable(estate->es_tupleTable, false);
+ /* Allow the FDW to shut down */
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(estate,
+ resultRelInfo);
+
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(mtstate, cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1178,1190 **** 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),
--- 1178,1183 ----
***************
*** 1374,1379 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1367,1373 ----
resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionIsValid = false;
}
/*
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 18,23 ****
--- 18,24 ----
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
***************
*** 158,170 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
proute->parent_child_tupconv_maps[i] =
convert_tuples_by_name(tupDesc, part_tupdesc,
gettext_noop("could not convert row type"));
-
- /*
- * Verify result relation is a valid target for an INSERT. An
- * UPDATE of a partition-key becomes a DELETE+INSERT operation, so
- * this check is required even when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
}
proute->partitions[i] = leaf_part_rri;
--- 159,164 ----
***************
*** 338,350 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
estate->es_instrument);
/*
- * Verify result relation is a valid target for an INSERT. An UPDATE of a
- * partition-key becomes a DELETE+INSERT operation, so this check is still
- * required when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
-
- /*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
*
--- 332,337 ----
***************
*** 461,466 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 448,454 ----
returningList = map_partition_varattnos(returningList, firstVarno,
partrel, firstResultRel,
NULL);
+ leaf_part_rri->ri_returningList = returningList;
/*
* Initialize the projection itself.
***************
*** 631,636 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 619,651 ----
}
/*
+ * ExecInitForeignRouting
+ * Let the FDW init itself for tuple routing for the partition
+ *
+ * We call this after performing CheckValidResultRel against the partition,
+ * avoid useless initialization for the FDW in ExecSetupPartitionTupleRouting
+ * and ExecInitPartitionInfo.
+ */
+ void
+ ExecInitForeignRouting(ModifyTableState *mtstate,
+ EState *estate,
+ ResultRelInfo *partRelInfo)
+ {
+ MemoryContext oldContext;
+
+ /*
+ * Switch into per-query memory context.
+ */
+ oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ if (partRelInfo->ri_FdwRoutine != NULL &&
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
+
+ MemoryContextSwitchTo(oldContext);
+ }
+
+ /*
* ExecSetupChildParentMapForLeaf -- Initialize the per-leaf-partition
* child-to-root tuple conversion map array.
*
***************
*** 732,738 **** ConvertPartitionTupleSlot(TupleConversionMap *map,
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
--- 747,754 ----
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
***************
*** 760,765 **** ExecCleanupTupleRouting(PartitionTupleRouting *proute)
--- 776,788 ----
if (resultRelInfo == NULL)
continue;
+ /* Allow any FDWs to shut down if they've been initialized */
+ if (resultRelInfo->ri_PartitionIsValid &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(mtstate->ps.state,
+ resultRelInfo);
+
/*
* If this result rel is one of the UPDATE subplan result rels, let
* ExecEndPlan() close it. For INSERT or COPY,
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 1678,1688 **** ExecPrepareTupleRouting(ModifyTableState *mtstate,
proute, estate,
partidx);
! /* We do not yet have a way to insert into a foreign partition */
! if (partrel->ri_FdwRoutine)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/*
* Make it look like we are inserting into the partition.
--- 1678,1703 ----
proute, estate,
partidx);
! /*
! * Verify the specified partition is a valid target for INSERT if we
! * didn't yet.
! *
! * Note: an UPDATE of a partition-key becomes a DELETE+INSERT operation,
! * so this check is required even when the mtstate operation is
! * CMD_UPDATE. The reason we do this check here rather than in
! * ExecSetupPartitionTupleRouting() is to avoid aborting the UPDATE
! * unnecessarily due to non-routable subplan partitions that may not be
! * chosen for tuple routing after all.
! */
! if (!partrel->ri_PartitionIsValid)
! {
! CheckValidResultRel(partrel, CMD_INSERT);
!
! partrel->ri_PartitionIsValid = true;
!
! /* Let the FDW init itself for tuple routing. */
! ExecInitForeignRouting(mtstate, estate, partrel);
! }
/*
* Make it look like we are inserting into the partition.
***************
*** 2355,2360 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 2370,2376 ----
{
List *rlist = (List *) lfirst(l);
+ resultRelInfo->ri_returningList = rlist;
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
***************
*** 2637,2643 **** ExecEndModifyTable(ModifyTableState *node)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node->mt_partition_tuple_routing);
/*
* Free the exprcontext
--- 2653,2659 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
/*
* Free the exprcontext
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 118,123 **** extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 118,126 ----
ResultRelInfo *resultRelInfo,
PartitionTupleRouting *proute,
EState *estate, int partidx);
+ extern void ExecInitForeignRouting(ModifyTableState *mtstate,
+ EState *estate,
+ ResultRelInfo *partRelInfo);
extern void ExecSetupChildParentMapForLeaf(PartitionTupleRouting *proute);
extern TupleConversionMap *TupConvMapForLeaf(PartitionTupleRouting *proute,
ResultRelInfo *rootRelInfo, int leaf_index);
***************
*** 125,130 **** extern HeapTuple ConvertPartitionTupleSlot(TupleConversionMap *map,
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
--- 128,134 ----
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 97,102 **** typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
--- 97,108 ----
typedef void (*EndForeignModify_function) (EState *estate,
ResultRelInfo *rinfo);
+ typedef void (*BeginForeignInsert_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+
+ typedef void (*EndForeignInsert_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
typedef int (*IsForeignRelUpdatable_function) (Relation rel);
typedef bool (*PlanDirectModify_function) (PlannerInfo *root,
***************
*** 204,209 **** typedef struct FdwRoutine
--- 210,217 ----
ExecForeignUpdate_function ExecForeignUpdate;
ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify;
+ BeginForeignInsert_function BeginForeignInsert;
+ EndForeignInsert_function EndForeignInsert;
IsForeignRelUpdatable_function IsForeignRelUpdatable;
PlanDirectModify_function PlanDirectModify;
BeginDirectModify_function BeginDirectModify;
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 435,440 **** typedef struct ResultRelInfo
--- 435,443 ----
/* for removing junk attributes from tuples */
JunkFilter *ri_junkFilter;
+ /* list of RETURNING expressions */
+ List *ri_returningList;
+
/* for computing a RETURNING list */
ProjectionInfo *ri_projectReturning;
***************
*** 452,457 **** typedef struct ResultRelInfo
--- 455,463 ----
/* relation descriptor for root partitioned table */
Relation ri_PartitionRoot;
+
+ /* true if valid target for tuple routing */
+ bool ri_PartitionIsValid;
} ResultRelInfo;
/* ----------------
(2018/03/19 20:25), Amit Langote wrote:
On 2018/02/27 21:01, Etsuro Fujita wrote:
Attached is a new version of the patch set.
* Comments postgres-fdw-refactoring-1.patch:
1. Just a minor nitpick: wouldn't it be better to call it
create_foreign_modify_state just like its "finish" counterpart that's
named finish_foreign_modify?
Good idea! Done.
* Comments on foreign-routing-fdwapi-1.patch:
1. In the following sentence, s/rows/a tuple/g, to be consistent with
other text added by the patch+<para> + If the<function>ExecForeignRouting</function> pointer is set to +<literal>NULL</literal>, attempts to route rows to the foreign table will fail + with an error message. +</para>
I modified the patch to use the existing API ExecForeignInsert instead
of that API and removed that API including this doc.
2. If I understand the description you provided in [1] correctly, the
point of having ri_PartitionIsValid and ExecInitExtraPartitionInfo() is to
avoid possibly-redundantly performing following two steps in
ExecSetupPartitionTupleRouting() for *reused* partition ResultRelInfos
that may not be used for tuple routing after all:- create the parent_child_tupconv_maps[] entry for it
- perform FDW tuple routing initialization.
Sorry, my explanation was not enough, but that was just one of the
reasons why I introduced those; another is to do CheckValidResultRel
against a partition after the partition was chosen for tuple routing
rather than in ExecSetupPartitionTupleRouting, to avoid aborting an
UPDATE of a partition key unnecessarily due to non-routable
foreign-partitions that may not be chosen for tuple routing after all.
Now we have ON CONFLICT for partitioned tables, which requires the
conversion map to be computed in ExecInitPartitionInfo, so I updated the
patch so that we keep the former step in ExecInitPartitionInfo and
ExecSetupPartitionTupleRouting and so that we just init the FDW in
ExecInitExtraPartitionInfo (changed to ExecInitForeignRouting). And I
added comments to ExecInitForeignRouting and ExecPrepareTupleRouting.
3. You removed the following from ExecInitPartitionInfo:
/*
- * Verify result relation is a valid target for an INSERT. An UPDATE
of a
- * partition-key becomes a DELETE+INSERT operation, so this check is
still
- * required when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);but, only added back the following in ExecInsert:
+ /* + * Verify the specified partition is a valid target for INSERT if we + * didn't yet. + */ + if (!resultRelInfo->ri_PartitionIsValid) + { + CheckValidResultRel(resultRelInfo, CMD_INSERT);Maybe, the new comment should add a "Note: " line the comment saying
something about this code being invoked as part of an UPDATE.
Done.
Also, I fixed a bug reported from you the way you proposed [1]/messages/by-id/2d72275d-3574-92c9-9241-5c9b456c87a2@lab.ntt.co.jp, and
added regression tests for that. Good catch! Thanks!
Thank you for the review!
Best regards,
Etsuro Fujita
[1]: /messages/by-id/2d72275d-3574-92c9-9241-5c9b456c87a2@lab.ntt.co.jp
/messages/by-id/2d72275d-3574-92c9-9241-5c9b456c87a2@lab.ntt.co.jp
(2018/03/20 21:31), Alvaro Herrera wrote:
Amit Langote wrote:
2. If I understand the description you provided in [1] correctly, the
point of having ri_PartitionIsValid and ExecInitExtraPartitionInfo() is to
avoid possibly-redundantly performing following two steps in
ExecSetupPartitionTupleRouting() for *reused* partition ResultRelInfos
that may not be used for tuple routing after all:- create the parent_child_tupconv_maps[] entry for it
- perform FDW tuple routing initialization.If that's true, the following comment could be expanded just a little bit
to make that clearer:/*
* ExecInitPartitionExtraInfo
* Do the remaining initialization work for the given partitionYeah, I think the comments on why this is a separate step should be more
extensive. Probably add something to ExecInitPartitionInfo too.
Done. Please see the reply to Amit that I sent just now.
Thank you for the review!
Best regards,
Etsuro Fujita
Etsuro Fujita wrote:
Now we have ON CONFLICT for partitioned tables, which requires the
conversion map to be computed in ExecInitPartitionInfo, so I updated the
patch so that we keep the former step in ExecInitPartitionInfo and
ExecSetupPartitionTupleRouting and so that we just init the FDW in
ExecInitExtraPartitionInfo (changed to ExecInitForeignRouting). And I added
comments to ExecInitForeignRouting and ExecPrepareTupleRouting.
Hmm, I may be misreading what you mean here ... but as far as I
understand, ON CONFLICT does not support foreign tables, does it? If
that is so, I'm not sure why the conversion map would be necessary, if
it is for ON CONFLICT alone.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Fujita-san,
Thanks for updating the patch.
On 2018/03/30 21:56, Etsuro Fujita wrote:
I modified the patch to use the existing API ExecForeignInsert instead of
that API and removed that API including this doc.
OK.
2. If I understand the description you provided in [1] correctly, the
point of having ri_PartitionIsValid and ExecInitExtraPartitionInfo() is to
avoid possibly-redundantly performing following two steps in
ExecSetupPartitionTupleRouting() for *reused* partition ResultRelInfos
that may not be used for tuple routing after all:� - create the parent_child_tupconv_maps[] entry for it
� - perform FDW tuple routing initialization.Sorry, my explanation was not enough, but that was just one of the reasons
why I introduced those; another is to do CheckValidResultRel against a
partition after the partition was chosen for tuple routing rather than in
ExecSetupPartitionTupleRouting, to avoid aborting an UPDATE of a partition
key unnecessarily due to non-routable foreign-partitions that may not be
chosen for tuple routing after all.
I see. So, currently in ExecSetupPartitionTupleRouting, we call
CheckValidResultRel() to check if resultRelInfos reused from those
initialized for UPDATE are valid for insertions (or rather for routing
inserted tuples into it). But you're proposing to delay that check until
ExecPrepareTupleRouting is called for a given resultRelInfo, at which
point it's clear that we actually want to insert using that resultRelInfo.
That seems reasonable.
Now we have ON CONFLICT for partitioned tables, which requires the
conversion map to be computed in ExecInitPartitionInfo, so I updated the
patch so that we keep the former step in ExecInitPartitionInfo and
ExecSetupPartitionTupleRouting and so that we just init the FDW in
ExecInitExtraPartitionInfo (changed to ExecInitForeignRouting).� And I
added comments to ExecInitForeignRouting and ExecPrepareTupleRouting.
That looks good.
I looked at the new patch. It looks good overall, although I have one
question -- IIUC, BeginForeignInsert() performs actions that are
equivalent of performing PlanForeignModify() + BeginForeignModify() for an
INSERT as if it was directly executed on a given foreign table/partition.
Did you face any problems in doing the latter itself in the core code?
Doing it that way would mean no changes to a FDW itself will be required
(and hence no need for additional APIs), but I may be missing something.
One thing that seems good about your approach is that newly added support
for COPY FROM on foreign tables/partitions takes minimal changes as
implemented by using the new API, whereas with the alternative approach it
would require duplicated code (same code would have to be present in both
copy.c and execPartition.c, but it could perhaps be avoided).
Thanks,
Amit
(2018/03/30 22:28), Alvaro Herrera wrote:
Etsuro Fujita wrote:
Now we have ON CONFLICT for partitioned tables, which requires the
conversion map to be computed in ExecInitPartitionInfo, so I updated the
patch so that we keep the former step in ExecInitPartitionInfo and
ExecSetupPartitionTupleRouting and so that we just init the FDW in
ExecInitExtraPartitionInfo (changed to ExecInitForeignRouting). And I added
comments to ExecInitForeignRouting and ExecPrepareTupleRouting.Hmm, I may be misreading what you mean here ... but as far as I
understand, ON CONFLICT does not support foreign tables, does it?
It doesn't, except for ON CONFLICT DO NOTHING without an inference
specification.
If
that is so, I'm not sure why the conversion map would be necessary, if
it is for ON CONFLICT alone.
We wouldn't need that for foreign partitions (When DO NOTHING with an
inference specification or DO UPDATE on a partitioned table containing
foreign partitions, the planner would throw an error before we get to
ExecInitPartitionInfo). The reason why I updated the patch that way is
just to make the patch simple, but on reflection I don't think that's a
good idea; I think we should delay the map-creation step as well as the
FDW-initialization step for UPDATE subplan partitions as before for
improved efficiency for UPDATE-of-partition-key. However, I don't think
it'd still be a good idea to delay those steps for partitions created by
ExecInitPartitionInfo the same way as for the subplan partitions,
because in that case it'd be better to perform CheckValidResultRel
against a partition right after we do InitResultRelInfo for the
partition in ExecInitPartitionInfo, as that would save cycles in cases
when the partition is considered nonvalid by that check. So, What I'm
thinking is: a) for the subplan partitions, we delay those steps as
before, and b) for the partitions created by ExecInitPartitionInfo, we
do that check for a partition right after InitResultRelInfo in that
function, (and if valid, we create a map and initialize the FDW for the
partition in that function).
Best regards,
Etsuro Fujita
(2018/04/02 18:49), Amit Langote wrote:
2. If I understand the description you provided in [1] correctly, the
point of having ri_PartitionIsValid and ExecInitExtraPartitionInfo() is to
avoid possibly-redundantly performing following two steps in
ExecSetupPartitionTupleRouting() for *reused* partition ResultRelInfos
that may not be used for tuple routing after all:- create the parent_child_tupconv_maps[] entry for it
- perform FDW tuple routing initialization.Sorry, my explanation was not enough, but that was just one of the reasons
why I introduced those; another is to do CheckValidResultRel against a
partition after the partition was chosen for tuple routing rather than in
ExecSetupPartitionTupleRouting, to avoid aborting an UPDATE of a partition
key unnecessarily due to non-routable foreign-partitions that may not be
chosen for tuple routing after all.I see. So, currently in ExecSetupPartitionTupleRouting, we call
CheckValidResultRel() to check if resultRelInfos reused from those
initialized for UPDATE are valid for insertions (or rather for routing
inserted tuples into it). But you're proposing to delay that check until
ExecPrepareTupleRouting is called for a given resultRelInfo, at which
point it's clear that we actually want to insert using that resultRelInfo.
That seems reasonable.
That's exactly what I'm thinking.
Now we have ON CONFLICT for partitioned tables, which requires the
conversion map to be computed in ExecInitPartitionInfo, so I updated the
patch so that we keep the former step in ExecInitPartitionInfo and
ExecSetupPartitionTupleRouting and so that we just init the FDW in
ExecInitExtraPartitionInfo (changed to ExecInitForeignRouting). And I
added comments to ExecInitForeignRouting and ExecPrepareTupleRouting.That looks good.
I revisited this. Please see the reply to Alvaro I sent right now.
I looked at the new patch. It looks good overall, although I have one
question -- IIUC, BeginForeignInsert() performs actions that are
equivalent of performing PlanForeignModify() + BeginForeignModify() for an
INSERT as if it was directly executed on a given foreign table/partition.
Did you face any problems in doing the latter itself in the core code?
Doing it that way would mean no changes to a FDW itself will be required
(and hence no need for additional APIs), but I may be missing something.
The biggest issue in performing PlanForeignModify() plus
BeginForeignModify() instead of the new FDW API would be: can the core
code create a valid-looking planner state passed to PlanForeignModify()
such as the ModifyTable plan node or the query tree stored in PlannerInfo?
One thing that seems good about your approach is that newly added support
for COPY FROM on foreign tables/partitions takes minimal changes as
implemented by using the new API, whereas with the alternative approach it
would require duplicated code (same code would have to be present in both
copy.c and execPartition.c, but it could perhaps be avoided).
I agree.
Thanks for the review!
Best regards,
Etsuro Fujita
On 2018/04/02 21:26, Etsuro Fujita wrote:
(2018/03/30 22:28), Alvaro Herrera wrote:
Etsuro Fujita wrote:
Now we have ON CONFLICT for partitioned tables, which requires the
conversion map to be computed in ExecInitPartitionInfo, so I updated the
patch so that we keep the former step in ExecInitPartitionInfo and
ExecSetupPartitionTupleRouting and so that we just init the FDW in
ExecInitExtraPartitionInfo (changed to ExecInitForeignRouting). And I
added
comments to ExecInitForeignRouting and ExecPrepareTupleRouting.Hmm, I may be misreading what you mean here ... but as far as I
understand, ON CONFLICT does not support foreign tables, does it?It doesn't, except for ON CONFLICT DO NOTHING without an inference
specification.If
that is so, I'm not sure why the conversion map would be necessary, if
it is for ON CONFLICT alone.We wouldn't need that for foreign partitions (When DO NOTHING with an
inference specification or DO UPDATE on a partitioned table containing
foreign partitions, the planner would throw an error before we get to
ExecInitPartitionInfo).
Actually, as you might know, since it is not possible to create an index
on a partitioned table that has a foreign partition, there is no
possibility of supporting any form of ON CONFLICT that requires an
inference specification.
The reason why I updated the patch that way is
just to make the patch simple, but on reflection I don't think that's a
good idea; I think we should delay the map-creation step as well as the
FDW-initialization step for UPDATE subplan partitions as before for
improved efficiency for UPDATE-of-partition-key. However, I don't think
it'd still be a good idea to delay those steps for partitions created by
ExecInitPartitionInfo the same way as for the subplan partitions, because
in that case it'd be better to perform CheckValidResultRel against a
partition right after we do InitResultRelInfo for the partition in
ExecInitPartitionInfo, as that would save cycles in cases when the
partition is considered nonvalid by that check.
It seems like a good idea to perform CheckValidResultRel right after the
InitResultRelInfo in ExecInitPartitionInfo. However, if the partition is
considered non-valid, that results in an error afaik, so I don't
understand the point about saving cycles.
So, What I'm thinking is:
a) for the subplan partitions, we delay those steps as before, and b) for
the partitions created by ExecInitPartitionInfo, we do that check for a
partition right after InitResultRelInfo in that function, (and if valid,
we create a map and initialize the FDW for the partition in that function).
Sounds good to me. I'm assuming that you're talking about delaying the
is-valid-for-insert-routing check (using CheckValidResultRel) and
parent-to-child map creation for a sub-plan result relation until
ExecPrepareTupleRouting().
On a related note, I wonder if it'd be a good idea to rename the flag
ri_PartitionIsValid to something that signifies that we only need it to be
true if we want to do tuple routing (aka insert) using it. Maybe,
ri_PartitionReadyForRouting or ri_PartitionReadyForInsert. I'm afraid
that ri_PartitionIsValid is a bit ambiguous, although I'm not saying the
name options I'm suggesting are the best.
Thanks,
Amit
Fujita-san,
On 2018/04/02 21:29, Etsuro Fujita wrote:
(2018/04/02 18:49), Amit Langote wrote:
I looked at the new patch.� It looks good overall, although I have one
question -- IIUC, BeginForeignInsert() performs actions that are
equivalent of performing PlanForeignModify() + BeginForeignModify() for an
INSERT as if it was directly executed on a given foreign table/partition.
Did you face any problems in doing the latter itself in the core code?
Doing it that way would mean no changes to a FDW itself will be required
(and hence no need for additional APIs), but I may be missing something.The biggest issue in performing PlanForeignModify() plus
BeginForeignModify() instead of the new FDW API would be: can the core
code create a valid-looking planner state passed to PlanForeignModify()
such as the ModifyTable plan node or the query tree stored in PlannerInfo?
Hmm, I can see the point. Passing mostly-dummy (garbage) PlannerInfo and
query tree from the core code to FDW seems bad. By defining the new API
with a clean interface (receiving fully valid ModifyTableState), we're not
required to do that across the interface, but only inside the FDW's
implementation. I was just slightly concerned that the new FDW function
would have to implement what would normally be carried out across multiple
special purpose callbacks, but maybe that's OK as long as it's clearly
documented what its job is.
Noticed a couple of things in the patch:
+ <para>
+ When this is called by a <command>COPY FROM</command> command, the
+ plan-related global data in <literal>mtstate</literal> is not provided
+ and the <literal>planSlot</literal> parameter of
+ <function>ExecForeignInsert</function> called for each inserted
tuple is
How about s/called/subsequently called/g?
+ <literal>NULL</literal>, wether the foreign table is the partition
chosen
Typo: s/wether/whether/g
Thanks,
Amit
(2018/04/03 13:32), Amit Langote wrote:
On 2018/04/02 21:26, Etsuro Fujita wrote:
We wouldn't need that for foreign partitions (When DO NOTHING with an
inference specification or DO UPDATE on a partitioned table containing
foreign partitions, the planner would throw an error before we get to
ExecInitPartitionInfo).Actually, as you might know, since it is not possible to create an index
on a partitioned table that has a foreign partition, there is no
possibility of supporting any form of ON CONFLICT that requires an
inference specification.
Right.
The reason why I updated the patch that way is
just to make the patch simple, but on reflection I don't think that's a
good idea; I think we should delay the map-creation step as well as the
FDW-initialization step for UPDATE subplan partitions as before for
improved efficiency for UPDATE-of-partition-key. However, I don't think
it'd still be a good idea to delay those steps for partitions created by
ExecInitPartitionInfo the same way as for the subplan partitions, because
in that case it'd be better to perform CheckValidResultRel against a
partition right after we do InitResultRelInfo for the partition in
ExecInitPartitionInfo, as that would save cycles in cases when the
partition is considered nonvalid by that check.It seems like a good idea to perform CheckValidResultRel right after the
InitResultRelInfo in ExecInitPartitionInfo. However, if the partition is
considered non-valid, that results in an error afaik, so I don't
understand the point about saving cycles.
I think that we could abort the query without doing the remaining work
after the check in ExecInitPartitionInfo in that case.
So, What I'm thinking is:
a) for the subplan partitions, we delay those steps as before, and b) for
the partitions created by ExecInitPartitionInfo, we do that check for a
partition right after InitResultRelInfo in that function, (and if valid,
we create a map and initialize the FDW for the partition in that function).Sounds good to me. I'm assuming that you're talking about delaying the
is-valid-for-insert-routing check (using CheckValidResultRel) and
parent-to-child map creation for a sub-plan result relation until
ExecPrepareTupleRouting().
That's exactly what I have in mind. I modified the patch that way.
On a related note, I wonder if it'd be a good idea to rename the flag
ri_PartitionIsValid to something that signifies that we only need it to be
true if we want to do tuple routing (aka insert) using it. Maybe,
ri_PartitionReadyForRouting or ri_PartitionReadyForInsert. I'm afraid
that ri_PartitionIsValid is a bit ambiguous, although I'm not saying the
name options I'm suggesting are the best.
That's a good idea! I like the first one, so I changed the name to that.
Thanks for the review!
Attached is an updated version of the patch. Patch
foreign-routing-fdwapi-3.patch is created on top of patch
postgres-fdw-refactoring-3.patch and the bug-fix patch [1]/messages/by-id/5ABA4074.1090500@lab.ntt.co.jp.
Other changes:
* Fixed/revised docs as you pointed out in another post.
* Added docs to update.sgml
* Revised some code/comments a little bit
* Revised regression tests
* Rebased against the latest HEAD
Best regards,
Etsuro Fujita
Attachments:
postgres-fdw-refactoring-3.patchtext/x-diff; name=postgres-fdw-refactoring-3.patchDownload
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 376,387 **** static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
--- 376,396 ----
static void create_cursor(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
+ static PgFdwModifyState *create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs);
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
+ static void finish_foreign_modify(PgFdwModifyState *fmstate);
static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
***************
*** 1681,1698 **** postgresBeginForeignModify(ModifyTableState *mtstate,
int eflags)
{
PgFdwModifyState *fmstate;
! EState *estate = mtstate->ps.state;
! CmdType operation = mtstate->operation;
! Relation rel = resultRelInfo->ri_RelationDesc;
! RangeTblEntry *rte;
! Oid userid;
! ForeignTable *table;
! UserMapping *user;
! AttrNumber n_params;
! Oid typefnoid;
! bool isvarlena;
! ListCell *lc;
! TupleDesc tupdesc = RelationGetDescr(rel);
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
--- 1690,1699 ----
int eflags)
{
PgFdwModifyState *fmstate;
! char *query;
! List *target_attrs;
! bool has_returning;
! List *retrieved_attrs;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
***************
*** 1701,1782 **** postgresBeginForeignModify(ModifyTableState *mtstate,
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
- /* Begin constructing PgFdwModifyState. */
- fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
- fmstate->rel = rel;
-
- /*
- * Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
- */
- rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
- userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
- /* Get info about foreign table. */
- table = GetForeignTable(RelationGetRelid(rel));
- user = GetUserMapping(userid, table->serverid);
-
- /* Open connection; report that we'll create a prepared statement. */
- fmstate->conn = GetConnection(user, true);
- fmstate->p_name = NULL; /* prepared statement not made yet */
-
/* Deconstruct fdw_private data. */
! fmstate->query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! fmstate->target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! fmstate->has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! fmstate->retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
!
! /* Create context for per-tuple temp workspace. */
! fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
! "postgres_fdw temporary data",
! ALLOCSET_SMALL_SIZES);
!
! /* Prepare for input conversion of RETURNING results. */
! if (fmstate->has_returning)
! fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
!
! /* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 1;
! fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
! fmstate->p_nums = 0;
!
! if (operation == CMD_UPDATE || operation == CMD_DELETE)
! {
! /* Find the ctid resjunk column in the subplan's result */
! Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
!
! fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
! "ctid");
! if (!AttributeNumberIsValid(fmstate->ctidAttno))
! elog(ERROR, "could not find junk ctid column");
! /* First transmittable parameter will be ctid */
! getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
!
! if (operation == CMD_INSERT || operation == CMD_UPDATE)
! {
! /* Set up for remaining transmittable parameters */
! foreach(lc, fmstate->target_attrs)
! {
! int attnum = lfirst_int(lc);
! Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
!
! Assert(!attr->attisdropped);
!
! getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
! }
!
! Assert(fmstate->p_nums <= n_params);
resultRelInfo->ri_FdwState = fmstate;
}
--- 1702,1726 ----
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/* Deconstruct fdw_private data. */
! query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
! /* Construct an execution state. */
! fmstate = create_foreign_modify(mtstate->ps.state,
! resultRelInfo,
! mtstate->operation,
! mtstate->mt_plans[subplan_index]->plan,
! query,
! target_attrs,
! has_returning,
! retrieved_attrs);
resultRelInfo->ri_FdwState = fmstate;
}
***************
*** 2011,2038 **** postgresEndForeignModify(EState *estate,
if (fmstate == NULL)
return;
! /* If we created a prepared statement, destroy it */
! if (fmstate->p_name)
! {
! char sql[64];
! PGresult *res;
!
! snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
!
! /*
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_exec_query(fmstate->conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
! PQclear(res);
! fmstate->p_name = NULL;
! }
!
! /* Release remote connection */
! ReleaseConnection(fmstate->conn);
! fmstate->conn = NULL;
}
/*
--- 1955,1962 ----
if (fmstate == NULL)
return;
! /* Destroy the execution state */
! finish_foreign_modify(fmstate);
}
/*
***************
*** 3229,3234 **** close_cursor(PGconn *conn, unsigned int cursor_number)
--- 3153,3261 ----
}
/*
+ * create_foreign_modify
+ * Construct an execution state of a foreign insert/update/delete
+ * operation
+ */
+ static PgFdwModifyState *
+ create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs)
+ {
+ PgFdwModifyState *fmstate;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Oid userid;
+ ForeignTable *table;
+ UserMapping *user;
+ AttrNumber n_params;
+ Oid typefnoid;
+ bool isvarlena;
+ ListCell *lc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+
+ /* Begin constructing PgFdwModifyState. */
+ fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
+ fmstate->rel = rel;
+
+ /*
+ * Identify which user to do the remote access as. This should match what
+ * ExecCheckRTEPerms() does.
+ */
+ rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ /* Get info about foreign table. */
+ table = GetForeignTable(RelationGetRelid(rel));
+ user = GetUserMapping(userid, table->serverid);
+
+ /* Open connection; report that we'll create a prepared statement. */
+ fmstate->conn = GetConnection(user, true);
+ fmstate->p_name = NULL; /* prepared statement not made yet */
+
+ /* Set up remote query information. */
+ fmstate->query = query;
+ fmstate->target_attrs = target_attrs;
+ fmstate->has_returning = has_returning;
+ fmstate->retrieved_attrs = retrieved_attrs;
+
+ /* Create context for per-tuple temp workspace. */
+ fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+ "postgres_fdw temporary data",
+ ALLOCSET_SMALL_SIZES);
+
+ /* Prepare for input conversion of RETURNING results. */
+ if (fmstate->has_returning)
+ fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ /* Prepare for output conversion of parameters used in prepared stmt. */
+ n_params = list_length(fmstate->target_attrs) + 1;
+ fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
+ fmstate->p_nums = 0;
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ Assert(subplan != NULL);
+
+ /* Find the ctid resjunk column in the subplan's result */
+ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "ctid");
+ if (!AttributeNumberIsValid(fmstate->ctidAttno))
+ elog(ERROR, "could not find junk ctid column");
+
+ /* First transmittable parameter will be ctid */
+ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ /* Set up for remaining transmittable parameters */
+ foreach(lc, fmstate->target_attrs)
+ {
+ int attnum = lfirst_int(lc);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ Assert(!attr->attisdropped);
+
+ getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+ }
+
+ Assert(fmstate->p_nums <= n_params);
+
+ return fmstate;
+ }
+
+ /*
* prepare_foreign_modify
* Establish a prepared statement for execution of INSERT/UPDATE/DELETE
*/
***************
*** 3371,3376 **** store_returning_result(PgFdwModifyState *fmstate,
--- 3398,3436 ----
}
/*
+ * finish_foreign_modify
+ * Release resources for a foreign insert/update/delete operation
+ */
+ static void
+ finish_foreign_modify(PgFdwModifyState *fmstate)
+ {
+ Assert(fmstate != NULL);
+
+ /* If we created a prepared statement, destroy it */
+ if (fmstate->p_name)
+ {
+ char sql[64];
+ PGresult *res;
+
+ snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
+
+ /*
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_exec_query(fmstate->conn, sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
+ PQclear(res);
+ fmstate->p_name = NULL;
+ }
+
+ /* Release remote connection */
+ ReleaseConnection(fmstate->conn);
+ fmstate->conn = NULL;
+ }
+
+ /*
* build_remote_returning
* Build a RETURNING targetlist of a remote query for performing an
* UPDATE/DELETE .. RETURNING on a join directly
foreign-routing-fdwapi-3.patchtext/x-diff; name=foreign-routing-fdwapi-3.patchDownload
*** 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 insert into foreign table "p1"
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
***************
*** 342,351 **** SELECT tableoid::regclass, * FROM p2;
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
--- 342,351 ----
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot insert into foreign table "p1"
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot insert into foreign table "p1"
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7371,7376 **** NOTICE: drop cascades to foreign table bar2
--- 7371,7710 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ -- Test insert tuple routing
+ create table itrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+ insert into itrtest values (1, 'foo');
+ insert into itrtest values (1, 'bar') returning *;
+ a | b
+ ---+-----
+ 1 | bar
+ (1 row)
+
+ insert into itrtest values (2, 'baz');
+ insert into itrtest values (2, 'qux') returning *;
+ a | b
+ ---+-----
+ 2 | qux
+ (1 row)
+
+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
+ a | b
+ ---+-------
+ 1 | test1
+ 2 | test2
+ (2 rows)
+
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b
+ ----------+---+-------
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ remp1 | 1 | test1
+ remp2 | 2 | baz
+ remp2 | 2 | qux
+ remp2 | 2 | test2
+ (6 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-------
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ remp1 | 1 | test1
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | a
+ ----------+-------+---
+ remp2 | baz | 2
+ remp2 | qux | 2
+ remp2 | test2 | 2
+ (3 rows)
+
+ delete from itrtest;
+ create unique index loct1_idx on loct1 (a);
+ -- DO NOTHING without an inference specification is supported
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ a | b
+ ---+-----
+ 1 | foo
+ (1 row)
+
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ a | b
+ ---+---
+ (0 rows)
+
+ -- But other cases are not supported
+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ (1 row)
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+ -- Test update tuple routing
+ create table utrtest (a int, b text) partition by list (a);
+ create table loct (a int check (a in (1)), b text);
+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+ insert into utrtest values (1, 'foo');
+ insert into utrtest values (2, 'qux');
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ locp | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+-----
+ locp | 2 | qux
+ (1 row)
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where b = 'foo' returning *;
+ ERROR: new row for relation "loct" violates check constraint "loct_a_check"
+ DETAIL: Failing row contains (2, foo).
+ CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
+ -- But the reverse is allowed
+ update utrtest set a = 1 where b = 'qux' returning *;
+ a | b
+ ---+-----
+ 1 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ remp | 1 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ remp | 1 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ (0 rows)
+
+ -- The executor should not let unexercised FDWs shut down
+ update utrtest set a = 1 where b = 'foo';
+ drop table utrtest;
+ drop table loct;
+ -- Test copy tuple routing
+ create table ctrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+ copy ctrtest from stdin;
+ select tableoid::regclass, * FROM ctrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ remp2 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | a
+ ----------+-----+---
+ remp2 | qux | 2
+ (1 row)
+
+ -- Copying into foreign partitions directly should work as well
+ copy remp1 from stdin;
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ (2 rows)
+
+ drop table ctrtest;
+ drop table loct1;
+ drop table loct2;
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+ -- Test basic functionality
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ delete from rem2;
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+ -- check constraint is enforced on the remote side, not locally
+ copy rem2 from stdin;
+ copy rem2 from stdin; -- ERROR
+ ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive"
+ DETAIL: Failing row contains (-1, xyzzy).
+ CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2)
+ COPY rem2, line 1: "-1 xyzzy"
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+ delete from rem2;
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ copy rem2 from stdin;
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+ delete from rem2;
+ create trigger trig_row_before_insupdate before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insupdate on rem2;
+ delete from rem2;
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on rem2;
+ delete from rem2;
+ -- Test remote triggers
+ create trigger trig_row_before_insupdate before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insupdate on loc2;
+ delete from rem2;
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on loc2;
+ delete from rem2;
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insupdate before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ copy rem2 from stdin;
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,"foo triggered !")
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,"bar triggered !")
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insupdate on loc2;
+ delete from rem2;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 319,324 **** static TupleTableSlot *postgresExecForeignDelete(EState *estate,
--- 319,328 ----
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
+ static void postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo);
+ static void postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo);
static int postgresIsForeignRelUpdatable(Relation rel);
static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
***************
*** 473,478 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 477,484 ----
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
+ routine->BeginForeignInsert = postgresBeginForeignInsert;
+ routine->EndForeignInsert = postgresEndForeignInsert;
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
routine->PlanDirectModify = postgresPlanDirectModify;
routine->BeginDirectModify = postgresBeginDirectModify;
***************
*** 1960,1965 **** postgresEndForeignModify(EState *estate,
--- 1966,2061 ----
}
/*
+ * postgresBeginForeignInsert
+ * Begin an insert operation on a foreign table
+ */
+ static void
+ postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate;
+ Plan *plan = mtstate->ps.plan;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Query *query;
+ PlannerInfo *root;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int attnum;
+ StringInfoData sql;
+ List *targetAttrs = NIL;
+ List *retrieved_attrs = NIL;
+ bool doNothing = false;
+
+ initStringInfo(&sql);
+
+ /* Set up largely-dummy planner state. */
+ rte = makeNode(RangeTblEntry);
+ rte->rtekind = RTE_RELATION;
+ rte->relid = RelationGetRelid(rel);
+ rte->relkind = RELKIND_FOREIGN_TABLE;
+ query = makeNode(Query);
+ query->commandType = CMD_INSERT;
+ query->resultRelation = 1;
+ query->rtable = list_make1(rte);
+ root = makeNode(PlannerInfo);
+ root->parse = query;
+
+ /* We transmit all columns that are defined in the foreign table. */
+ for (attnum = 1; attnum <= tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (!attr->attisdropped)
+ targetAttrs = lappend_int(targetAttrs, attnum);
+ }
+
+ /* Check if we add the ON CONFLICT clause to the remote query. */
+ if (plan)
+ {
+ OnConflictAction onConflictAction = ((ModifyTable *) plan)->onConflictAction;
+
+ /* We only support DO NOTHING without an inference specification. */
+ if (onConflictAction == ONCONFLICT_NOTHING)
+ doNothing = true;
+ else if (onConflictAction != ONCONFLICT_NONE)
+ elog(ERROR, "unexpected ON CONFLICT specification: %d",
+ (int) onConflictAction);
+ }
+
+ /* Construct the SQL command string. */
+ deparseInsertSql(&sql, root, 1, rel, targetAttrs, doNothing,
+ resultRelInfo->ri_returningList, &retrieved_attrs);
+
+ /* Construct an execution state. */
+ fmstate = create_foreign_modify(mtstate->ps.state,
+ resultRelInfo,
+ CMD_INSERT,
+ plan,
+ sql.data,
+ targetAttrs,
+ retrieved_attrs != NIL,
+ retrieved_attrs);
+
+ resultRelInfo->ri_FdwState = fmstate;
+ }
+
+ /*
+ * postgresEndForeignInsert
+ * Finish an insert operation on a foreign table
+ */
+ static void
+ postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+
+ Assert(fmstate != NULL);
+
+ /* Destroy the execution state */
+ finish_foreign_modify(fmstate);
+ }
+
+ /*
* postgresIsForeignRelUpdatable
* Determine whether a foreign table supports INSERT, UPDATE and/or
* DELETE.
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1768,1773 **** drop table loct1;
--- 1768,2010 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ -- Test insert tuple routing
+ create table itrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+
+ insert into itrtest values (1, 'foo');
+ insert into itrtest values (1, 'bar') returning *;
+ insert into itrtest values (2, 'baz');
+ insert into itrtest values (2, 'qux') returning *;
+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
+
+ select tableoid::regclass, * FROM itrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ delete from itrtest;
+
+ create unique index loct1_idx on loct1 (a);
+
+ -- DO NOTHING without an inference specification is supported
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+
+ -- But other cases are not supported
+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
+
+ select tableoid::regclass, * FROM itrtest;
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test update tuple routing
+ create table utrtest (a int, b text) partition by list (a);
+ create table loct (a int check (a in (1)), b text);
+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+
+ insert into utrtest values (1, 'foo');
+ insert into utrtest values (2, 'qux');
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where b = 'foo' returning *;
+
+ -- But the reverse is allowed
+ update utrtest set a = 1 where b = 'qux' returning *;
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- The executor should not let unexercised FDWs shut down
+ update utrtest set a = 1 where b = 'foo';
+
+ drop table utrtest;
+ drop table loct;
+
+ -- Test copy tuple routing
+ create table ctrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+
+ copy ctrtest from stdin;
+ 1 foo
+ 2 qux
+ \.
+
+ select tableoid::regclass, * FROM ctrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ -- Copying into foreign partitions directly should work as well
+ copy remp1 from stdin;
+ 1 bar
+ \.
+
+ select tableoid::regclass, * FROM remp1;
+
+ drop table ctrtest;
+ drop table loct1;
+ drop table loct2;
+
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+
+ -- Test basic functionality
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ delete from rem2;
+
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+
+ -- check constraint is enforced on the remote side, not locally
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ copy rem2 from stdin; -- ERROR
+ -1 xyzzy
+ \.
+ select * from rem2;
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+
+ delete from rem2;
+
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+
+ delete from rem2;
+
+ create trigger trig_row_before_insupdate before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insupdate on rem2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on rem2;
+
+ delete from rem2;
+
+ -- Test remote triggers
+ create trigger trig_row_before_insupdate before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insupdate on loc2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on loc2;
+
+ delete from rem2;
+
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insupdate before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insupdate on loc2;
+
+ delete from rem2;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 3037,3047 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<para>
--- 3037,3045 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<para>
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 695,700 **** EndForeignModify(EState *estate,
--- 695,765 ----
</para>
<para>
+ Tuples inserted into a partitioned table are routed to partitions. If an
+ FDW supports routable foreign-table partitions, it should also provide
+ the following callback functions. These functions are also called when
+ <command>COPY FROM</command> is executed on a foreign table.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ BeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ Begin executing an insert operation on a foreign table. This routine is
+ called right before the first tuple is inserted into the foreign table
+ in both cases where it is the partition chosen for tuple routing and the
+ target specified in a <command>COPY FROM</command> command. It should
+ perform any initialization needed prior to the actual insertion.
+ Subsequently, <function>ExecForeignInsert</function> will be called for
+ each tuple to be inserted into the foreign table.
+ </para>
+
+ <para>
+ <literal>mtstate</literal> is the overall state of the
+ <structname>ModifyTable</structname> plan node being executed; global data about
+ the plan and execution state is available via this structure.
+ <literal>rinfo</literal> is the <structname>ResultRelInfo</structname> struct describing
+ the target foreign table. (The <structfield>ri_FdwState</structfield> field of
+ <structname>ResultRelInfo</structname> is available for the FDW to store any
+ private state it needs for this operation.)
+ </para>
+
+ <para>
+ When this is called by a <command>COPY FROM</command> command, the
+ plan-related global data in <literal>mtstate</literal> is not provided
+ and the <literal>planSlot</literal> parameter of
+ <function>ExecForeignInsert</function> subsequently called for each
+ inserted tuple is <literal>NULL</literal>, whether the foreign table is
+ the partition chosen for tuple routing or the target specified in the
+ command.
+ </para>
+
+ <para>
+ If the <function>BeginForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the initialization.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ EndForeignInsert(EState *estate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ End the insert operation and release resources. It is normally not important
+ to release palloc'd memory, but for example open files and connections
+ to remote servers should be cleaned up.
+ </para>
+
+ <para>
+ If the <function>EndForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the termination.
+ </para>
+
+ <para>
<programlisting>
int
IsForeignRelUpdatable(Relation rel);
*** a/doc/src/sgml/ref/copy.sgml
--- b/doc/src/sgml/ref/copy.sgml
***************
*** 402,409 **** COPY <replaceable class="parameter">count</replaceable>
</para>
<para>
! <command>COPY FROM</command> can be used with plain tables and with views
! that have <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
--- 402,410 ----
</para>
<para>
! <command>COPY FROM</command> can be used with plain, foreign, or
! partitioned tables and with views that have
! <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 291,296 **** UPDATE <replaceable class="parameter">count</replaceable>
--- 291,299 ----
concurrent <command>UPDATE</command> or <command>DELETE</command> on the
same row may miss this row. For details see the section
<xref linkend="ddl-partitioning-declarative-limitations"/>.
+ Currently, it is not allowed to move a row from a partition that is a
+ foreign table to another, but the reverse is allowed if the foreign table
+ is routable.
</para>
</refsect1>
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 29,34 ****
--- 29,35 ----
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
***************
*** 2284,2289 **** CopyFrom(CopyState cstate)
--- 2285,2291 ----
ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
+ ModifyTableState *mtstate;
ExprContext *econtext;
TupleTableSlot *myslot;
MemoryContext oldcontext = CurrentMemoryContext;
***************
*** 2305,2315 **** CopyFrom(CopyState cstate)
Assert(cstate->rel);
/*
! * The target must be a plain relation or have an INSTEAD OF INSERT row
! * trigger. (Currently, such triggers are only allowed on views, so we
! * only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
--- 2307,2318 ----
Assert(cstate->rel);
/*
! * The target must be a plain, foreign, or partitioned relation, or have
! * an INSTEAD OF INSERT row trigger. (Currently, such triggers are only
! * allowed on views, so we only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
***************
*** 2325,2335 **** CopyFrom(CopyState cstate)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy to materialized view \"%s\"",
RelationGetRelationName(cstate->rel))));
- else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy to foreign table \"%s\"",
- RelationGetRelationName(cstate->rel))));
else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
--- 2328,2333 ----
***************
*** 2448,2453 **** CopyFrom(CopyState cstate)
--- 2446,2466 ----
/* Triggers might need a slot as well */
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+ /*
+ * Set up a ModifyTableState so we can let FDW(s) init themselves for
+ * foreign-table result relation(s).
+ */
+ mtstate = makeNode(ModifyTableState);
+ mtstate->ps.plan = NULL;
+ mtstate->ps.state = estate;
+ mtstate->operation = CMD_INSERT;
+ mtstate->resultRelInfo = estate->es_result_relations;
+
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate,
+ resultRelInfo);
+
/* Prepare to catch AFTER triggers. */
AfterTriggerBeginQuery();
***************
*** 2489,2499 **** CopyFrom(CopyState cstate)
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
--- 2502,2513 ----
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is foreign or partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ resultRelInfo->ri_FdwRoutine != NULL ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
***************
*** 2608,2626 **** CopyFrom(CopyState cstate)
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
! resultRelInfo = ExecInitPartitionInfo(NULL,
saved_resultRelInfo,
proute, estate,
leaf_part_index);
Assert(resultRelInfo != NULL);
}
- /* 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
*/
--- 2622,2634 ----
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
! resultRelInfo = ExecInitPartitionInfo(mtstate,
saved_resultRelInfo,
proute, estate,
leaf_part_index);
Assert(resultRelInfo != NULL);
}
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
*/
***************
*** 2708,2716 **** CopyFrom(CopyState cstate)
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /* Check the constraints of the tuple */
! if (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
--- 2716,2728 ----
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /*
! * If the target is a plain table, check the constraints of
! * the tuple.
! */
! if (resultRelInfo->ri_FdwRoutine == NULL &&
! (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr))
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
***************
*** 2742,2751 **** CopyFrom(CopyState cstate)
{
List *recheckIndexes = NIL;
! /* OK, store the tuple and create index entries for it */
! heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
! hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
--- 2754,2785 ----
{
List *recheckIndexes = NIL;
! /* OK, store the tuple */
! if (resultRelInfo->ri_FdwRoutine != NULL)
! {
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! NULL);
!
! if (slot == NULL) /* "do nothing" */
! goto next_tuple;
!
! /* FDW might have changed tuple */
! tuple = ExecMaterializeSlot(slot);
!
! /*
! * AFTER ROW Triggers might reference the tableoid
! * column, so initialize t_tableOid before evaluating
! * them.
! */
! tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
! }
! else
! heap_insert(resultRelInfo->ri_RelationDesc, tuple,
! mycid, hi_options, bistate);
+ /* And create index entries for it */
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
***************
*** 2763,2775 **** CopyFrom(CopyState cstate)
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger;
! * this is the same definition used by execMain.c for counting
! * tuples inserted by an INSERT command.
*/
processed++;
}
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
--- 2797,2810 ----
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger
! * or FDW; this is the same definition used by nodeModifyTable.c
! * for counting tuples inserted by an INSERT command.
*/
processed++;
}
+ next_tuple:
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
***************
*** 2810,2820 **** CopyFrom(CopyState cstate)
ExecResetTupleTable(estate->es_tupleTable, false);
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
--- 2845,2861 ----
ExecResetTupleTable(estate->es_tupleTable, false);
+ /* Allow the FDW to shut down */
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(estate,
+ resultRelInfo);
+
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(mtstate, cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1179,1191 **** 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),
--- 1179,1184 ----
***************
*** 1378,1383 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1371,1377 ----
resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionReadyForRouting = false;
}
/*
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 18,23 ****
--- 18,24 ----
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
***************
*** 55,66 **** static List *adjust_partition_tlist(List *tlist, TupleConversionMap *map);
* see ExecInitPartitionInfo. However, if the function is invoked for update
* tuple routing, caller would already have initialized ResultRelInfo's for
* some of the partitions, which are reused and assigned to their respective
! * slot in the aforementioned array.
*/
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
- TupleDesc tupDesc = RelationGetDescr(rel);
List *leaf_parts;
ListCell *cell;
int i;
--- 56,68 ----
* see ExecInitPartitionInfo. However, if the function is invoked for update
* tuple routing, caller would already have initialized ResultRelInfo's for
* some of the partitions, which are reused and assigned to their respective
! * slot in the aforementioned array. For such partitions, we delay setting
! * up objects such as TupleConversionMap until those are actually chosen as
! * the partitions to route tuples to. See ExecPrepareTupleRouting.
*/
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
List *leaf_parts;
ListCell *cell;
int i;
***************
*** 141,151 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
if (update_rri_index < num_update_rri &&
RelationGetRelid(update_rri[update_rri_index].ri_RelationDesc) == leaf_oid)
{
- Relation partrel;
- TupleDesc part_tupdesc;
-
leaf_part_rri = &update_rri[update_rri_index];
- partrel = leaf_part_rri->ri_RelationDesc;
/*
* This is required in order to convert the partition's tuple to
--- 143,149 ----
***************
*** 159,181 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
proute->subplan_partition_offsets[update_rri_index] = i;
update_rri_index++;
-
- 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.
- */
- proute->parent_child_tupconv_maps[i] =
- convert_tuples_by_name(tupDesc, part_tupdesc,
- gettext_noop("could not convert row type"));
-
- /*
- * Verify result relation is a valid target for an INSERT. An
- * UPDATE of a partition-key becomes a DELETE+INSERT operation, so
- * this check is required even when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
}
proute->partitions[i] = leaf_part_rri;
--- 157,162 ----
***************
*** 342,351 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
PartitionTupleRouting *proute,
EState *estate, int partidx)
{
Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel;
ResultRelInfo *leaf_part_rri;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
MemoryContext oldContext;
/*
--- 323,332 ----
PartitionTupleRouting *proute,
EState *estate, int partidx)
{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel;
ResultRelInfo *leaf_part_rri;
MemoryContext oldContext;
/*
***************
*** 369,379 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
leaf_part_rri->ri_PartitionLeafIndex = partidx;
! /*
! * Verify result relation is a valid target for an INSERT. An UPDATE of a
! * partition-key becomes a DELETE+INSERT operation, so this check is still
! * required when the operation is CMD_UPDATE.
! */
CheckValidResultRel(leaf_part_rri, CMD_INSERT);
/*
--- 350,356 ----
leaf_part_rri->ri_PartitionLeafIndex = partidx;
! /* Verify the specified partition is a valid target for INSERT */
CheckValidResultRel(leaf_part_rri, CMD_INSERT);
/*
***************
*** 388,393 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 365,373 ----
lappend(estate->es_tuple_routing_result_relations,
leaf_part_rri);
+ /* Set up information for routing tuples to the specified partition */
+ ExecInitRoutingInfo(mtstate, estate, proute, leaf_part_rri, partidx);
+
/*
* Open partition indices. The user may have asked to check for conflicts
* within this leaf partition and do "nothing" instead of throwing an
***************
*** 493,498 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 473,479 ----
returningList = map_partition_varattnos(returningList, firstVarno,
partrel, firstResultRel,
NULL);
+ leaf_part_rri->ri_returningList = returningList;
/*
* Initialize the projection itself.
***************
*** 510,524 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
}
/*
- * Save a tuple conversion map to convert a tuple routed to this partition
- * from the parent's type to the partition's.
- */
- proute->parent_child_tupconv_maps[partidx] =
- convert_tuples_by_name(RelationGetDescr(rootrel),
- RelationGetDescr(partrel),
- gettext_noop("could not convert row type"));
-
- /*
* If there is an ON CONFLICT clause, initialize state for it.
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
--- 491,496 ----
***************
*** 654,659 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 626,633 ----
}
}
+ leaf_part_rri->ri_PartitionReadyForRouting = true;
+
Assert(proute->partitions[partidx] == NULL);
proute->partitions[partidx] = leaf_part_rri;
***************
*** 747,752 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 721,764 ----
}
/*
+ * ExecInitRoutingInfo
+ * Prepare a tuple conversion map for the given partition, and if it is
+ * a foreign table, let the FDW init itself for the result relation.
+ */
+ void
+ ExecInitRoutingInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx)
+ {
+ MemoryContext oldContext;
+
+ /*
+ * Switch into per-query memory context.
+ */
+ oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Set up a tuple conversion map to convert a tuple routed to the
+ * partition from the parent's type to the partition's.
+ */
+ proute->parent_child_tupconv_maps[partidx] =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
+ RelationGetDescr(partRelInfo->ri_RelationDesc),
+ gettext_noop("could not convert row type"));
+
+ /*
+ * Let the FDW init itself for routing tuples to the partition.
+ */
+ if (partRelInfo->ri_FdwRoutine != NULL &&
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
+
+ MemoryContextSwitchTo(oldContext);
+ }
+
+ /*
* ExecSetupChildParentMapForLeaf -- Initialize the per-leaf-partition
* child-to-root tuple conversion map array.
*
***************
*** 848,854 **** ConvertPartitionTupleSlot(TupleConversionMap *map,
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
--- 860,867 ----
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
***************
*** 876,881 **** ExecCleanupTupleRouting(PartitionTupleRouting *proute)
--- 889,901 ----
if (resultRelInfo == NULL)
continue;
+ /* Allow any FDWs to shut down if they've been exercised */
+ if (resultRelInfo->ri_PartitionReadyForRouting &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(mtstate->ps.state,
+ resultRelInfo);
+
/*
* If this result rel is one of the UPDATE subplan result rels, let
* ExecEndPlan() close it. For INSERT or COPY,
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 1831,1841 **** ExecPrepareTupleRouting(ModifyTableState *mtstate,
proute, estate,
partidx);
! /* We do not yet have a way to insert into a foreign partition */
! if (partrel->ri_FdwRoutine)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/*
* Make it look like we are inserting into the partition.
--- 1831,1856 ----
proute, estate,
partidx);
! /*
! * Verify the partition is a valid target for INSERT if we didn't yet.
! *
! * Note: an UPDATE of a partition key invokes an INSERT that moves the
! * tuple to a new partition. This check would be applied to a subplan
! * partition of such an UPDATE that is chosen as the partition to move
! * the tuple to. The reason we do this check here rather than in
! * ExecSetupPartitionTupleRouting is to avoid aborting such an UPDATE
! * unnecessarily due to non-routable subplan partitions that may not be
! * chosen for update tuple movement after all.
! */
! if (!partrel->ri_PartitionReadyForRouting)
! {
! CheckValidResultRel(partrel, CMD_INSERT);
!
! /* OK, set up information for routing tuples to the partition */
! ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
!
! partrel->ri_PartitionReadyForRouting = true;
! }
/*
* Make it look like we are inserting into the partition.
***************
*** 2536,2541 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 2551,2557 ----
{
List *rlist = (List *) lfirst(l);
+ resultRelInfo->ri_returningList = rlist;
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
***************
*** 2931,2937 **** ExecEndModifyTable(ModifyTableState *node)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node->mt_partition_tuple_routing);
/*
* Free the exprcontext
--- 2947,2953 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
/*
* Free the exprcontext
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 119,124 **** extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 119,129 ----
ResultRelInfo *resultRelInfo,
PartitionTupleRouting *proute,
EState *estate, int partidx);
+ extern void ExecInitRoutingInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx);
extern void ExecSetupChildParentMapForLeaf(PartitionTupleRouting *proute);
extern TupleConversionMap *TupConvMapForLeaf(PartitionTupleRouting *proute,
ResultRelInfo *rootRelInfo, int leaf_index);
***************
*** 126,131 **** extern HeapTuple ConvertPartitionTupleSlot(TupleConversionMap *map,
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
--- 131,137 ----
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 98,103 **** typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
--- 98,109 ----
typedef void (*EndForeignModify_function) (EState *estate,
ResultRelInfo *rinfo);
+ typedef void (*BeginForeignInsert_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+
+ typedef void (*EndForeignInsert_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
typedef int (*IsForeignRelUpdatable_function) (Relation rel);
typedef bool (*PlanDirectModify_function) (PlannerInfo *root,
***************
*** 205,210 **** typedef struct FdwRoutine
--- 211,218 ----
ExecForeignUpdate_function ExecForeignUpdate;
ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify;
+ BeginForeignInsert_function BeginForeignInsert;
+ EndForeignInsert_function EndForeignInsert;
IsForeignRelUpdatable_function IsForeignRelUpdatable;
PlanDirectModify_function PlanDirectModify;
BeginDirectModify_function BeginDirectModify;
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 444,449 **** typedef struct ResultRelInfo
--- 444,452 ----
/* for removing junk attributes from tuples */
JunkFilter *ri_junkFilter;
+ /* list of RETURNING expressions */
+ List *ri_returningList;
+
/* for computing a RETURNING list */
ProjectionInfo *ri_projectReturning;
***************
*** 462,467 **** typedef struct ResultRelInfo
--- 465,473 ----
/* relation descriptor for root partitioned table */
Relation ri_PartitionRoot;
+ /* true if valid target for tuple routing */
+ bool ri_PartitionReadyForRouting;
+
int ri_PartitionLeafIndex;
/* for running MERGE on this result relation */
MergeState *ri_mergeState;
(2018/04/03 13:59), Amit Langote wrote:
On 2018/04/02 21:29, Etsuro Fujita wrote:
(2018/04/02 18:49), Amit Langote wrote:
I looked at the new patch. It looks good overall, although I have one
question -- IIUC, BeginForeignInsert() performs actions that are
equivalent of performing PlanForeignModify() + BeginForeignModify() for an
INSERT as if it was directly executed on a given foreign table/partition.
Did you face any problems in doing the latter itself in the core code?
Doing it that way would mean no changes to a FDW itself will be required
(and hence no need for additional APIs), but I may be missing something.The biggest issue in performing PlanForeignModify() plus
BeginForeignModify() instead of the new FDW API would be: can the core
code create a valid-looking planner state passed to PlanForeignModify()
such as the ModifyTable plan node or the query tree stored in PlannerInfo?Hmm, I can see the point. Passing mostly-dummy (garbage) PlannerInfo and
query tree from the core code to FDW seems bad. By defining the new API
with a clean interface (receiving fully valid ModifyTableState), we're not
required to do that across the interface, but only inside the FDW's
implementation.
I really think so too.
I was just slightly concerned that the new FDW function
would have to implement what would normally be carried out across multiple
special purpose callbacks, but maybe that's OK as long as it's clearly
documented what its job is.
OK
Noticed a couple of things in the patch:
+<para> + When this is called by a<command>COPY FROM</command> command, the + plan-related global data in<literal>mtstate</literal> is not provided + and the<literal>planSlot</literal> parameter of +<function>ExecForeignInsert</function> called for each inserted tuple isHow about s/called/subsequently called/g?
Done.
+<literal>NULL</literal>, wether the foreign table is the partition
chosenTypo: s/wether/whether/g
Done.
Thanks again, Amit!
Best regards,
Etsuro Fujita
(2018/04/03 22:01), Etsuro Fujita wrote:
Attached is an updated version of the patch. Patch
foreign-routing-fdwapi-3.patch is created on top of patch
postgres-fdw-refactoring-3.patch and the bug-fix patch [1].
One thing I noticed about patch foreign-routing-fdwapi-3.patch is this
bug: the server will crash when copying data into a foreign table that
doesn't support the proposed APIs (eg, file_fdw foreign tables). The
reason is that the patch doesn't perform CheckValidResultRel before that
operation in that case. So I modified the patch as such and added
regression tests for that.
Attached is an updated version of the patch set:
* As before, patch foreign-routing-fdwapi-4.patch is created on top of
patch postgres-fdw-refactoring-4.patch and the bug-fix patch [1]/messages/by-id/5ABA4074.1090500@lab.ntt.co.jp.
* I revised comments, docs, and regression tests a bit further, but no
code changes other than the bug fix.
Best regards,
Etsuro Fujita
Attachments:
postgres-fdw-refactoring-4.patchtext/x-diff; name=postgres-fdw-refactoring-4.patchDownload
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 376,387 **** static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
--- 376,396 ----
static void create_cursor(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
+ static PgFdwModifyState *create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs);
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
+ static void finish_foreign_modify(PgFdwModifyState *fmstate);
static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
***************
*** 1681,1698 **** postgresBeginForeignModify(ModifyTableState *mtstate,
int eflags)
{
PgFdwModifyState *fmstate;
! EState *estate = mtstate->ps.state;
! CmdType operation = mtstate->operation;
! Relation rel = resultRelInfo->ri_RelationDesc;
! RangeTblEntry *rte;
! Oid userid;
! ForeignTable *table;
! UserMapping *user;
! AttrNumber n_params;
! Oid typefnoid;
! bool isvarlena;
! ListCell *lc;
! TupleDesc tupdesc = RelationGetDescr(rel);
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
--- 1690,1699 ----
int eflags)
{
PgFdwModifyState *fmstate;
! char *query;
! List *target_attrs;
! bool has_returning;
! List *retrieved_attrs;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
***************
*** 1701,1782 **** postgresBeginForeignModify(ModifyTableState *mtstate,
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
- /* Begin constructing PgFdwModifyState. */
- fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
- fmstate->rel = rel;
-
- /*
- * Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
- */
- rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
- userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
- /* Get info about foreign table. */
- table = GetForeignTable(RelationGetRelid(rel));
- user = GetUserMapping(userid, table->serverid);
-
- /* Open connection; report that we'll create a prepared statement. */
- fmstate->conn = GetConnection(user, true);
- fmstate->p_name = NULL; /* prepared statement not made yet */
-
/* Deconstruct fdw_private data. */
! fmstate->query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! fmstate->target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! fmstate->has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! fmstate->retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
!
! /* Create context for per-tuple temp workspace. */
! fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
! "postgres_fdw temporary data",
! ALLOCSET_SMALL_SIZES);
!
! /* Prepare for input conversion of RETURNING results. */
! if (fmstate->has_returning)
! fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
!
! /* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 1;
! fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
! fmstate->p_nums = 0;
!
! if (operation == CMD_UPDATE || operation == CMD_DELETE)
! {
! /* Find the ctid resjunk column in the subplan's result */
! Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
!
! fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
! "ctid");
! if (!AttributeNumberIsValid(fmstate->ctidAttno))
! elog(ERROR, "could not find junk ctid column");
! /* First transmittable parameter will be ctid */
! getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
!
! if (operation == CMD_INSERT || operation == CMD_UPDATE)
! {
! /* Set up for remaining transmittable parameters */
! foreach(lc, fmstate->target_attrs)
! {
! int attnum = lfirst_int(lc);
! Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
!
! Assert(!attr->attisdropped);
!
! getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
! }
!
! Assert(fmstate->p_nums <= n_params);
resultRelInfo->ri_FdwState = fmstate;
}
--- 1702,1726 ----
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/* Deconstruct fdw_private data. */
! query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
! /* Construct an execution state. */
! fmstate = create_foreign_modify(mtstate->ps.state,
! resultRelInfo,
! mtstate->operation,
! mtstate->mt_plans[subplan_index]->plan,
! query,
! target_attrs,
! has_returning,
! retrieved_attrs);
resultRelInfo->ri_FdwState = fmstate;
}
***************
*** 2011,2038 **** postgresEndForeignModify(EState *estate,
if (fmstate == NULL)
return;
! /* If we created a prepared statement, destroy it */
! if (fmstate->p_name)
! {
! char sql[64];
! PGresult *res;
!
! snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
!
! /*
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_exec_query(fmstate->conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
! PQclear(res);
! fmstate->p_name = NULL;
! }
!
! /* Release remote connection */
! ReleaseConnection(fmstate->conn);
! fmstate->conn = NULL;
}
/*
--- 1955,1962 ----
if (fmstate == NULL)
return;
! /* Destroy the execution state */
! finish_foreign_modify(fmstate);
}
/*
***************
*** 3229,3234 **** close_cursor(PGconn *conn, unsigned int cursor_number)
--- 3153,3261 ----
}
/*
+ * create_foreign_modify
+ * Construct an execution state of a foreign insert/update/delete
+ * operation
+ */
+ static PgFdwModifyState *
+ create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs)
+ {
+ PgFdwModifyState *fmstate;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ RangeTblEntry *rte;
+ Oid userid;
+ ForeignTable *table;
+ UserMapping *user;
+ AttrNumber n_params;
+ Oid typefnoid;
+ bool isvarlena;
+ ListCell *lc;
+
+ /* Begin constructing PgFdwModifyState. */
+ fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
+ fmstate->rel = rel;
+
+ /*
+ * Identify which user to do the remote access as. This should match what
+ * ExecCheckRTEPerms() does.
+ */
+ rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ /* Get info about foreign table. */
+ table = GetForeignTable(RelationGetRelid(rel));
+ user = GetUserMapping(userid, table->serverid);
+
+ /* Open connection; report that we'll create a prepared statement. */
+ fmstate->conn = GetConnection(user, true);
+ fmstate->p_name = NULL; /* prepared statement not made yet */
+
+ /* Set up remote query information. */
+ fmstate->query = query;
+ fmstate->target_attrs = target_attrs;
+ fmstate->has_returning = has_returning;
+ fmstate->retrieved_attrs = retrieved_attrs;
+
+ /* Create context for per-tuple temp workspace. */
+ fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+ "postgres_fdw temporary data",
+ ALLOCSET_SMALL_SIZES);
+
+ /* Prepare for input conversion of RETURNING results. */
+ if (fmstate->has_returning)
+ fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ /* Prepare for output conversion of parameters used in prepared stmt. */
+ n_params = list_length(fmstate->target_attrs) + 1;
+ fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
+ fmstate->p_nums = 0;
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ Assert(subplan != NULL);
+
+ /* Find the ctid resjunk column in the subplan's result */
+ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "ctid");
+ if (!AttributeNumberIsValid(fmstate->ctidAttno))
+ elog(ERROR, "could not find junk ctid column");
+
+ /* First transmittable parameter will be ctid */
+ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ /* Set up for remaining transmittable parameters */
+ foreach(lc, fmstate->target_attrs)
+ {
+ int attnum = lfirst_int(lc);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ Assert(!attr->attisdropped);
+
+ getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+ }
+
+ Assert(fmstate->p_nums <= n_params);
+
+ return fmstate;
+ }
+
+ /*
* prepare_foreign_modify
* Establish a prepared statement for execution of INSERT/UPDATE/DELETE
*/
***************
*** 3371,3376 **** store_returning_result(PgFdwModifyState *fmstate,
--- 3398,3436 ----
}
/*
+ * finish_foreign_modify
+ * Release resources for a foreign insert/update/delete operation
+ */
+ static void
+ finish_foreign_modify(PgFdwModifyState *fmstate)
+ {
+ Assert(fmstate != NULL);
+
+ /* If we created a prepared statement, destroy it */
+ if (fmstate->p_name)
+ {
+ char sql[64];
+ PGresult *res;
+
+ snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
+
+ /*
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_exec_query(fmstate->conn, sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
+ PQclear(res);
+ fmstate->p_name = NULL;
+ }
+
+ /* Release remote connection */
+ ReleaseConnection(fmstate->conn);
+ fmstate->conn = NULL;
+ }
+
+ /*
* build_remote_returning
* Build a RETURNING targetlist of a remote query for performing an
* UPDATE/DELETE .. RETURNING on a join directly
foreign-routing-fdwapi-4.patchtext/x-diff; name=foreign-routing-fdwapi-4.patchDownload
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 136,141 **** DELETE FROM agg_csv WHERE a = 100;
--- 136,146 ----
-- but this should be allowed
SELECT * FROM agg_csv FOR UPDATE;
+ -- copy from isn't supported either
+ COPY agg_csv FROM STDIN;
+ 12 3.4
+ \.
+
-- constraint exclusion tests
\t on
EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 221,226 **** SELECT * FROM agg_csv FOR UPDATE;
--- 221,229 ----
42 | 324.78
(3 rows)
+ -- copy from isn't supported either
+ COPY agg_csv FROM STDIN;
+ ERROR: cannot insert into foreign table "agg_csv"
-- constraint exclusion tests
\t on
EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
***************
*** 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;
--- 318,324 ----
(0 rows)
COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
! ERROR: cannot insert into foreign table "p1"
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
***************
*** 342,351 **** SELECT tableoid::regclass, * FROM p2;
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
--- 345,354 ----
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot insert into foreign table "p1"
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot insert into foreign table "p1"
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7371,7376 **** NOTICE: drop cascades to foreign table bar2
--- 7371,7710 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ -- Test insert tuple routing
+ create table itrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+ insert into itrtest values (1, 'foo');
+ insert into itrtest values (1, 'bar') returning *;
+ a | b
+ ---+-----
+ 1 | bar
+ (1 row)
+
+ insert into itrtest values (2, 'baz');
+ insert into itrtest values (2, 'qux') returning *;
+ a | b
+ ---+-----
+ 2 | qux
+ (1 row)
+
+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
+ a | b
+ ---+-------
+ 1 | test1
+ 2 | test2
+ (2 rows)
+
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b
+ ----------+---+-------
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ remp1 | 1 | test1
+ remp2 | 2 | baz
+ remp2 | 2 | qux
+ remp2 | 2 | test2
+ (6 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-------
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ remp1 | 1 | test1
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | a
+ ----------+-------+---
+ remp2 | baz | 2
+ remp2 | qux | 2
+ remp2 | test2 | 2
+ (3 rows)
+
+ delete from itrtest;
+ create unique index loct1_idx on loct1 (a);
+ -- DO NOTHING without an inference specification is supported
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ a | b
+ ---+-----
+ 1 | foo
+ (1 row)
+
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ a | b
+ ---+---
+ (0 rows)
+
+ -- But other cases are not supported
+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ (1 row)
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+ -- Test update tuple routing
+ create table utrtest (a int, b text) partition by list (a);
+ create table loct (a int check (a in (1)), b text);
+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+ insert into utrtest values (1, 'foo');
+ insert into utrtest values (2, 'qux');
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ locp | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+-----
+ locp | 2 | qux
+ (1 row)
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where b = 'foo' returning *;
+ ERROR: new row for relation "loct" violates check constraint "loct_a_check"
+ DETAIL: Failing row contains (2, foo).
+ CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
+ -- But the reverse is allowed
+ update utrtest set a = 1 where b = 'qux' returning *;
+ a | b
+ ---+-----
+ 1 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ remp | 1 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ remp | 1 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ (0 rows)
+
+ -- The executor should not let unexercised FDWs shut down
+ update utrtest set a = 1 where b = 'foo';
+ drop table utrtest;
+ drop table loct;
+ -- Test copy tuple routing
+ create table ctrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+ copy ctrtest from stdin;
+ select tableoid::regclass, * FROM ctrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ remp2 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | a
+ ----------+-----+---
+ remp2 | qux | 2
+ (1 row)
+
+ -- Copying into foreign partitions directly should work as well
+ copy remp1 from stdin;
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ (2 rows)
+
+ drop table ctrtest;
+ drop table loct1;
+ drop table loct2;
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+ -- Test basic functionality
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ delete from rem2;
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+ -- check constraint is enforced on the remote side, not locally
+ copy rem2 from stdin;
+ copy rem2 from stdin; -- ERROR
+ ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive"
+ DETAIL: Failing row contains (-1, xyzzy).
+ CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2)
+ COPY rem2, line 1: "-1 xyzzy"
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+ delete from rem2;
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ copy rem2 from stdin;
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+ delete from rem2;
+ create trigger trig_row_before_insert before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insert on rem2;
+ delete from rem2;
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on rem2;
+ delete from rem2;
+ -- Test remote triggers
+ create trigger trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insert on loc2;
+ delete from rem2;
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on loc2;
+ delete from rem2;
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ copy rem2 from stdin;
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,"foo triggered !")
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,"bar triggered !")
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insert on loc2;
+ delete from rem2;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 319,324 **** static TupleTableSlot *postgresExecForeignDelete(EState *estate,
--- 319,328 ----
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
+ static void postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo);
+ static void postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo);
static int postgresIsForeignRelUpdatable(Relation rel);
static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
***************
*** 473,478 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 477,484 ----
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
+ routine->BeginForeignInsert = postgresBeginForeignInsert;
+ routine->EndForeignInsert = postgresEndForeignInsert;
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
routine->PlanDirectModify = postgresPlanDirectModify;
routine->BeginDirectModify = postgresBeginDirectModify;
***************
*** 1960,1965 **** postgresEndForeignModify(EState *estate,
--- 1966,2061 ----
}
/*
+ * postgresBeginForeignInsert
+ * Begin an insert operation on a foreign table
+ */
+ static void
+ postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate;
+ Plan *plan = mtstate->ps.plan;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Query *query;
+ PlannerInfo *root;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int attnum;
+ StringInfoData sql;
+ List *targetAttrs = NIL;
+ List *retrieved_attrs = NIL;
+ bool doNothing = false;
+
+ initStringInfo(&sql);
+
+ /* Set up largely-dummy planner state. */
+ rte = makeNode(RangeTblEntry);
+ rte->rtekind = RTE_RELATION;
+ rte->relid = RelationGetRelid(rel);
+ rte->relkind = RELKIND_FOREIGN_TABLE;
+ query = makeNode(Query);
+ query->commandType = CMD_INSERT;
+ query->resultRelation = 1;
+ query->rtable = list_make1(rte);
+ root = makeNode(PlannerInfo);
+ root->parse = query;
+
+ /* We transmit all columns that are defined in the foreign table. */
+ for (attnum = 1; attnum <= tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (!attr->attisdropped)
+ targetAttrs = lappend_int(targetAttrs, attnum);
+ }
+
+ /* Check if we add the ON CONFLICT clause to the remote query. */
+ if (plan)
+ {
+ OnConflictAction onConflictAction = ((ModifyTable *) plan)->onConflictAction;
+
+ /* We only support DO NOTHING without an inference specification. */
+ if (onConflictAction == ONCONFLICT_NOTHING)
+ doNothing = true;
+ else if (onConflictAction != ONCONFLICT_NONE)
+ elog(ERROR, "unexpected ON CONFLICT specification: %d",
+ (int) onConflictAction);
+ }
+
+ /* Construct the SQL command string. */
+ deparseInsertSql(&sql, root, 1, rel, targetAttrs, doNothing,
+ resultRelInfo->ri_returningList, &retrieved_attrs);
+
+ /* Construct an execution state. */
+ fmstate = create_foreign_modify(mtstate->ps.state,
+ resultRelInfo,
+ CMD_INSERT,
+ plan,
+ sql.data,
+ targetAttrs,
+ retrieved_attrs != NIL,
+ retrieved_attrs);
+
+ resultRelInfo->ri_FdwState = fmstate;
+ }
+
+ /*
+ * postgresEndForeignInsert
+ * Finish an insert operation on a foreign table
+ */
+ static void
+ postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+
+ Assert(fmstate != NULL);
+
+ /* Destroy the execution state */
+ finish_foreign_modify(fmstate);
+ }
+
+ /*
* postgresIsForeignRelUpdatable
* Determine whether a foreign table supports INSERT, UPDATE and/or
* DELETE.
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1768,1773 **** drop table loct1;
--- 1768,2010 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ -- Test insert tuple routing
+ create table itrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+
+ insert into itrtest values (1, 'foo');
+ insert into itrtest values (1, 'bar') returning *;
+ insert into itrtest values (2, 'baz');
+ insert into itrtest values (2, 'qux') returning *;
+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
+
+ select tableoid::regclass, * FROM itrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ delete from itrtest;
+
+ create unique index loct1_idx on loct1 (a);
+
+ -- DO NOTHING without an inference specification is supported
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+
+ -- But other cases are not supported
+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
+
+ select tableoid::regclass, * FROM itrtest;
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test update tuple routing
+ create table utrtest (a int, b text) partition by list (a);
+ create table loct (a int check (a in (1)), b text);
+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+
+ insert into utrtest values (1, 'foo');
+ insert into utrtest values (2, 'qux');
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where b = 'foo' returning *;
+
+ -- But the reverse is allowed
+ update utrtest set a = 1 where b = 'qux' returning *;
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- The executor should not let unexercised FDWs shut down
+ update utrtest set a = 1 where b = 'foo';
+
+ drop table utrtest;
+ drop table loct;
+
+ -- Test copy tuple routing
+ create table ctrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+
+ copy ctrtest from stdin;
+ 1 foo
+ 2 qux
+ \.
+
+ select tableoid::regclass, * FROM ctrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ -- Copying into foreign partitions directly should work as well
+ copy remp1 from stdin;
+ 1 bar
+ \.
+
+ select tableoid::regclass, * FROM remp1;
+
+ drop table ctrtest;
+ drop table loct1;
+ drop table loct2;
+
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+
+ -- Test basic functionality
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ delete from rem2;
+
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+
+ -- check constraint is enforced on the remote side, not locally
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ copy rem2 from stdin; -- ERROR
+ -1 xyzzy
+ \.
+ select * from rem2;
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+
+ delete from rem2;
+
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+
+ delete from rem2;
+
+ create trigger trig_row_before_insert before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insert on rem2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on rem2;
+
+ delete from rem2;
+
+ -- Test remote triggers
+ create trigger trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insert on loc2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on loc2;
+
+ delete from rem2;
+
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insert on loc2;
+
+ delete from rem2;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 3037,3047 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<para>
--- 3037,3045 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<para>
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 695,700 **** EndForeignModify(EState *estate,
--- 695,766 ----
</para>
<para>
+ Tuples inserted into a partitioned table by <command>INSERT</command> or
+ <command>COPY FROM</command> are routed to partitions. If an FDW
+ supports routable foreign-table partitions, it should also provide the
+ following callback functions. These functions are also called when
+ <command>COPY FROM</command> is executed on a foreign table.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ BeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ Begin executing an insert operation on a foreign table. This routine is
+ called right before the first tuple is inserted into the foreign table
+ in both cases when it is the partition chosen for tuple routing and the
+ target specified in a <command>COPY FROM</command> command. It should
+ perform any initialization needed prior to the actual insertion.
+ Subsequently, <function>ExecForeignInsert</function> will be called for
+ each tuple to be inserted into the foreign table.
+ </para>
+
+ <para>
+ <literal>mtstate</literal> is the overall state of the
+ <structname>ModifyTable</structname> plan node being executed; global data about
+ the plan and execution state is available via this structure.
+ <literal>rinfo</literal> is the <structname>ResultRelInfo</structname> struct describing
+ the target foreign table. (The <structfield>ri_FdwState</structfield> field of
+ <structname>ResultRelInfo</structname> is available for the FDW to store any
+ private state it needs for this operation.)
+ </para>
+
+ <para>
+ When this is called by a <command>COPY FROM</command> command, the
+ plan-related global data in <literal>mtstate</literal> is not provided
+ and the <literal>planSlot</literal> parameter of
+ <function>ExecForeignInsert</function> subsequently called for each
+ inserted tuple is <literal>NULL</literal>, whether the foreign table is
+ the partition chosen for tuple routing or the target specified in the
+ command.
+ </para>
+
+ <para>
+ If the <function>BeginForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the initialization.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ EndForeignInsert(EState *estate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ End the insert operation and release resources. It is normally not important
+ to release palloc'd memory, but for example open files and connections
+ to remote servers should be cleaned up.
+ </para>
+
+ <para>
+ If the <function>EndForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the termination.
+ </para>
+
+ <para>
<programlisting>
int
IsForeignRelUpdatable(Relation rel);
*** a/doc/src/sgml/ref/copy.sgml
--- b/doc/src/sgml/ref/copy.sgml
***************
*** 402,409 **** COPY <replaceable class="parameter">count</replaceable>
</para>
<para>
! <command>COPY FROM</command> can be used with plain tables and with views
! that have <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
--- 402,410 ----
</para>
<para>
! <command>COPY FROM</command> can be used with plain, foreign, or
! partitioned tables or with views that have
! <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 291,296 **** UPDATE <replaceable class="parameter">count</replaceable>
--- 291,299 ----
concurrent <command>UPDATE</command> or <command>DELETE</command> on the
same row may miss this row. For details see the section
<xref linkend="ddl-partitioning-declarative-limitations"/>.
+ Currently, it is not allowed to move a row from a partition that is a
+ foreign table to another, but the reverse is allowed if the foreign table
+ is routable.
</para>
</refsect1>
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 29,34 ****
--- 29,35 ----
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
***************
*** 2284,2289 **** CopyFrom(CopyState cstate)
--- 2285,2291 ----
ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
+ ModifyTableState *mtstate;
ExprContext *econtext;
TupleTableSlot *myslot;
MemoryContext oldcontext = CurrentMemoryContext;
***************
*** 2305,2315 **** CopyFrom(CopyState cstate)
Assert(cstate->rel);
/*
! * The target must be a plain relation or have an INSTEAD OF INSERT row
! * trigger. (Currently, such triggers are only allowed on views, so we
! * only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
--- 2307,2318 ----
Assert(cstate->rel);
/*
! * The target must be a plain, foreign, or partitioned relation, or have
! * an INSTEAD OF INSERT row trigger. (Currently, such triggers are only
! * allowed on views, so we only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
***************
*** 2325,2335 **** CopyFrom(CopyState cstate)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy to materialized view \"%s\"",
RelationGetRelationName(cstate->rel))));
- else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy to foreign table \"%s\"",
- RelationGetRelationName(cstate->rel))));
else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
--- 2328,2333 ----
***************
*** 2436,2441 **** CopyFrom(CopyState cstate)
--- 2434,2442 ----
NULL,
0);
+ /* Verify the named relation is a valid target for INSERT */
+ CheckValidResultRel(resultRelInfo, CMD_INSERT);
+
ExecOpenIndices(resultRelInfo, false);
estate->es_result_relations = resultRelInfo;
***************
*** 2448,2453 **** CopyFrom(CopyState cstate)
--- 2449,2469 ----
/* Triggers might need a slot as well */
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+ /*
+ * Set up a ModifyTableState so we can let FDW(s) init themselves for
+ * foreign-table result relation(s).
+ */
+ mtstate = makeNode(ModifyTableState);
+ mtstate->ps.plan = NULL;
+ mtstate->ps.state = estate;
+ mtstate->operation = CMD_INSERT;
+ mtstate->resultRelInfo = estate->es_result_relations;
+
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate,
+ resultRelInfo);
+
/* Prepare to catch AFTER triggers. */
AfterTriggerBeginQuery();
***************
*** 2489,2499 **** CopyFrom(CopyState cstate)
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
--- 2505,2516 ----
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is foreign or partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ resultRelInfo->ri_FdwRoutine != NULL ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
***************
*** 2608,2626 **** CopyFrom(CopyState cstate)
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
! resultRelInfo = ExecInitPartitionInfo(NULL,
saved_resultRelInfo,
proute, estate,
leaf_part_index);
Assert(resultRelInfo != NULL);
}
- /* 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
*/
--- 2625,2637 ----
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
! resultRelInfo = ExecInitPartitionInfo(mtstate,
saved_resultRelInfo,
proute, estate,
leaf_part_index);
Assert(resultRelInfo != NULL);
}
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
*/
***************
*** 2708,2716 **** CopyFrom(CopyState cstate)
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /* Check the constraints of the tuple */
! if (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
--- 2719,2731 ----
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /*
! * If the target is a plain table, check the constraints of
! * the tuple.
! */
! if (resultRelInfo->ri_FdwRoutine == NULL &&
! (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr))
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
***************
*** 2742,2751 **** CopyFrom(CopyState cstate)
{
List *recheckIndexes = NIL;
! /* OK, store the tuple and create index entries for it */
! heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
! hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
--- 2757,2788 ----
{
List *recheckIndexes = NIL;
! /* OK, store the tuple */
! if (resultRelInfo->ri_FdwRoutine != NULL)
! {
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! NULL);
!
! if (slot == NULL) /* "do nothing" */
! goto next_tuple;
!
! /* FDW might have changed tuple */
! tuple = ExecMaterializeSlot(slot);
+ /*
+ * AFTER ROW Triggers might reference the tableoid
+ * column, so initialize t_tableOid before evaluating
+ * them.
+ */
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+ else
+ heap_insert(resultRelInfo->ri_RelationDesc, tuple,
+ mycid, hi_options, bistate);
+
+ /* And create index entries for it */
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
***************
*** 2763,2775 **** CopyFrom(CopyState cstate)
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger;
! * this is the same definition used by execMain.c for counting
! * tuples inserted by an INSERT command.
*/
processed++;
}
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
--- 2800,2813 ----
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger
! * or FDW; this is the same definition used by nodeModifyTable.c
! * for counting tuples inserted by an INSERT command.
*/
processed++;
}
+ next_tuple:
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
***************
*** 2810,2820 **** CopyFrom(CopyState cstate)
ExecResetTupleTable(estate->es_tupleTable, false);
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
--- 2848,2864 ----
ExecResetTupleTable(estate->es_tupleTable, false);
+ /* Allow the FDW to shut down */
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(estate,
+ resultRelInfo);
+
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(mtstate, cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1179,1191 **** 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),
--- 1179,1184 ----
***************
*** 1378,1383 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1371,1377 ----
resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionReadyForRouting = false;
}
/*
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 18,23 ****
--- 18,24 ----
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
***************
*** 55,66 **** static List *adjust_partition_tlist(List *tlist, TupleConversionMap *map);
* see ExecInitPartitionInfo. However, if the function is invoked for update
* tuple routing, caller would already have initialized ResultRelInfo's for
* some of the partitions, which are reused and assigned to their respective
! * slot in the aforementioned array.
*/
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
- TupleDesc tupDesc = RelationGetDescr(rel);
List *leaf_parts;
ListCell *cell;
int i;
--- 56,68 ----
* see ExecInitPartitionInfo. However, if the function is invoked for update
* tuple routing, caller would already have initialized ResultRelInfo's for
* some of the partitions, which are reused and assigned to their respective
! * slot in the aforementioned array. For such partitions, we delay setting
! * up objects such as TupleConversionMap until those are actually chosen as
! * the partitions to route tuples to. See ExecPrepareTupleRouting.
*/
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
List *leaf_parts;
ListCell *cell;
int i;
***************
*** 141,151 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
if (update_rri_index < num_update_rri &&
RelationGetRelid(update_rri[update_rri_index].ri_RelationDesc) == leaf_oid)
{
- Relation partrel;
- TupleDesc part_tupdesc;
-
leaf_part_rri = &update_rri[update_rri_index];
- partrel = leaf_part_rri->ri_RelationDesc;
/*
* This is required in order to convert the partition's tuple to
--- 143,149 ----
***************
*** 159,181 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
proute->subplan_partition_offsets[update_rri_index] = i;
update_rri_index++;
-
- 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.
- */
- proute->parent_child_tupconv_maps[i] =
- convert_tuples_by_name(tupDesc, part_tupdesc,
- gettext_noop("could not convert row type"));
-
- /*
- * Verify result relation is a valid target for an INSERT. An
- * UPDATE of a partition-key becomes a DELETE+INSERT operation, so
- * this check is required even when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
}
proute->partitions[i] = leaf_part_rri;
--- 157,162 ----
***************
*** 342,351 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
PartitionTupleRouting *proute,
EState *estate, int partidx)
{
Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel;
ResultRelInfo *leaf_part_rri;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
MemoryContext oldContext;
/*
--- 323,332 ----
PartitionTupleRouting *proute,
EState *estate, int partidx)
{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel;
ResultRelInfo *leaf_part_rri;
MemoryContext oldContext;
/*
***************
*** 369,379 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
leaf_part_rri->ri_PartitionLeafIndex = partidx;
! /*
! * Verify result relation is a valid target for an INSERT. An UPDATE of a
! * partition-key becomes a DELETE+INSERT operation, so this check is still
! * required when the operation is CMD_UPDATE.
! */
CheckValidResultRel(leaf_part_rri, CMD_INSERT);
/*
--- 350,356 ----
leaf_part_rri->ri_PartitionLeafIndex = partidx;
! /* Verify the specified partition is a valid target for INSERT */
CheckValidResultRel(leaf_part_rri, CMD_INSERT);
/*
***************
*** 388,393 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 365,373 ----
lappend(estate->es_tuple_routing_result_relations,
leaf_part_rri);
+ /* Set up information for routing tuples to the specified partition */
+ ExecInitRoutingInfo(mtstate, estate, proute, leaf_part_rri, partidx);
+
/*
* Open partition indices. The user may have asked to check for conflicts
* within this leaf partition and do "nothing" instead of throwing an
***************
*** 493,498 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 473,479 ----
returningList = map_partition_varattnos(returningList, firstVarno,
partrel, firstResultRel,
NULL);
+ leaf_part_rri->ri_returningList = returningList;
/*
* Initialize the projection itself.
***************
*** 510,524 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
}
/*
- * Save a tuple conversion map to convert a tuple routed to this partition
- * from the parent's type to the partition's.
- */
- proute->parent_child_tupconv_maps[partidx] =
- convert_tuples_by_name(RelationGetDescr(rootrel),
- RelationGetDescr(partrel),
- gettext_noop("could not convert row type"));
-
- /*
* If there is an ON CONFLICT clause, initialize state for it.
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
--- 491,496 ----
***************
*** 654,659 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 626,633 ----
}
}
+ leaf_part_rri->ri_PartitionReadyForRouting = true;
+
Assert(proute->partitions[partidx] == NULL);
proute->partitions[partidx] = leaf_part_rri;
***************
*** 747,752 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 721,765 ----
}
/*
+ * ExecInitRoutingInfo
+ * Prepare a tuple conversion map for the given partition, and if it is
+ * a foreign table, let the FDW init itself for routing tuples to it.
+ */
+ void
+ ExecInitRoutingInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx)
+ {
+ MemoryContext oldContext;
+
+ /*
+ * Switch into per-query memory context.
+ */
+ oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Set up a tuple conversion map to convert a tuple routed to the
+ * partition from the parent's type to the partition's.
+ */
+ proute->parent_child_tupconv_maps[partidx] =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
+ RelationGetDescr(partRelInfo->ri_RelationDesc),
+ gettext_noop("could not convert row type"));
+
+ /*
+ * Let the FDW init itself for routing tuples to the foreign-table
+ * partition.
+ */
+ if (partRelInfo->ri_FdwRoutine != NULL &&
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
+
+ MemoryContextSwitchTo(oldContext);
+ }
+
+ /*
* ExecSetupChildParentMapForLeaf -- Initialize the per-leaf-partition
* child-to-root tuple conversion map array.
*
***************
*** 848,854 **** ConvertPartitionTupleSlot(TupleConversionMap *map,
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
--- 861,868 ----
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
***************
*** 876,881 **** ExecCleanupTupleRouting(PartitionTupleRouting *proute)
--- 890,902 ----
if (resultRelInfo == NULL)
continue;
+ /* Allow any FDWs to shut down if they've been exercised */
+ if (resultRelInfo->ri_PartitionReadyForRouting &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(mtstate->ps.state,
+ resultRelInfo);
+
/*
* If this result rel is one of the UPDATE subplan result rels, let
* ExecEndPlan() close it. For INSERT or COPY,
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 1831,1841 **** ExecPrepareTupleRouting(ModifyTableState *mtstate,
proute, estate,
partidx);
! /* We do not yet have a way to insert into a foreign partition */
! if (partrel->ri_FdwRoutine)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/*
* Make it look like we are inserting into the partition.
--- 1831,1856 ----
proute, estate,
partidx);
! /*
! * Verify the partition is a valid target for INSERT if we didn't yet.
! *
! * Note: an UPDATE of a partition key invokes an INSERT that moves the
! * tuple to a new partition. This check would be applied to a subplan
! * partition of such an UPDATE that is chosen as the partition to move
! * the tuple to. The reason we do this check here rather than in
! * ExecSetupPartitionTupleRouting is to avoid aborting such an UPDATE
! * unnecessarily due to non-routable subplan partitions that may not be
! * chosen for update tuple movement after all.
! */
! if (!partrel->ri_PartitionReadyForRouting)
! {
! CheckValidResultRel(partrel, CMD_INSERT);
!
! /* OK, set up information for routing tuples to the partition */
! ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
!
! partrel->ri_PartitionReadyForRouting = true;
! }
/*
* Make it look like we are inserting into the partition.
***************
*** 2536,2541 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 2551,2557 ----
{
List *rlist = (List *) lfirst(l);
+ resultRelInfo->ri_returningList = rlist;
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
***************
*** 2931,2937 **** ExecEndModifyTable(ModifyTableState *node)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node->mt_partition_tuple_routing);
/*
* Free the exprcontext
--- 2947,2953 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
/*
* Free the exprcontext
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 119,124 **** extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 119,129 ----
ResultRelInfo *resultRelInfo,
PartitionTupleRouting *proute,
EState *estate, int partidx);
+ extern void ExecInitRoutingInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx);
extern void ExecSetupChildParentMapForLeaf(PartitionTupleRouting *proute);
extern TupleConversionMap *TupConvMapForLeaf(PartitionTupleRouting *proute,
ResultRelInfo *rootRelInfo, int leaf_index);
***************
*** 126,131 **** extern HeapTuple ConvertPartitionTupleSlot(TupleConversionMap *map,
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
--- 131,137 ----
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 98,103 **** typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
--- 98,109 ----
typedef void (*EndForeignModify_function) (EState *estate,
ResultRelInfo *rinfo);
+ typedef void (*BeginForeignInsert_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+
+ typedef void (*EndForeignInsert_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
typedef int (*IsForeignRelUpdatable_function) (Relation rel);
typedef bool (*PlanDirectModify_function) (PlannerInfo *root,
***************
*** 205,210 **** typedef struct FdwRoutine
--- 211,218 ----
ExecForeignUpdate_function ExecForeignUpdate;
ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify;
+ BeginForeignInsert_function BeginForeignInsert;
+ EndForeignInsert_function EndForeignInsert;
IsForeignRelUpdatable_function IsForeignRelUpdatable;
PlanDirectModify_function PlanDirectModify;
BeginDirectModify_function BeginDirectModify;
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 444,449 **** typedef struct ResultRelInfo
--- 444,452 ----
/* for removing junk attributes from tuples */
JunkFilter *ri_junkFilter;
+ /* list of RETURNING expressions */
+ List *ri_returningList;
+
/* for computing a RETURNING list */
ProjectionInfo *ri_projectReturning;
***************
*** 462,467 **** typedef struct ResultRelInfo
--- 465,473 ----
/* relation descriptor for root partitioned table */
Relation ri_PartitionRoot;
+ /* true if ready for tuple routing */
+ bool ri_PartitionReadyForRouting;
+
int ri_PartitionLeafIndex;
/* for running MERGE on this result relation */
MergeState *ri_mergeState;
(2018/04/04 19:31), Etsuro Fujita wrote:
Attached is an updated version of the patch set:
* As before, patch foreign-routing-fdwapi-4.patch is created on top of
patch postgres-fdw-refactoring-4.patch and the bug-fix patch [1].
I did a bit of cleanup and comment-rewording to patch
foreign-routing-fdwapi-4.patch. Attached is a new version of the patch
set. I renamed the postgres_fdw refactoring patch but no changes to
that patch.
Best regards,
Etsuro Fujita
Attachments:
postgres-fdw-refactoring-5.patchtext/x-diff; name=postgres-fdw-refactoring-5.patchDownload
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 376,387 **** static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
--- 376,396 ----
static void create_cursor(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
+ static PgFdwModifyState *create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs);
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
+ static void finish_foreign_modify(PgFdwModifyState *fmstate);
static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
***************
*** 1681,1698 **** postgresBeginForeignModify(ModifyTableState *mtstate,
int eflags)
{
PgFdwModifyState *fmstate;
! EState *estate = mtstate->ps.state;
! CmdType operation = mtstate->operation;
! Relation rel = resultRelInfo->ri_RelationDesc;
! RangeTblEntry *rte;
! Oid userid;
! ForeignTable *table;
! UserMapping *user;
! AttrNumber n_params;
! Oid typefnoid;
! bool isvarlena;
! ListCell *lc;
! TupleDesc tupdesc = RelationGetDescr(rel);
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
--- 1690,1699 ----
int eflags)
{
PgFdwModifyState *fmstate;
! char *query;
! List *target_attrs;
! bool has_returning;
! List *retrieved_attrs;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
***************
*** 1701,1782 **** postgresBeginForeignModify(ModifyTableState *mtstate,
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
- /* Begin constructing PgFdwModifyState. */
- fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
- fmstate->rel = rel;
-
- /*
- * Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
- */
- rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
- userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
- /* Get info about foreign table. */
- table = GetForeignTable(RelationGetRelid(rel));
- user = GetUserMapping(userid, table->serverid);
-
- /* Open connection; report that we'll create a prepared statement. */
- fmstate->conn = GetConnection(user, true);
- fmstate->p_name = NULL; /* prepared statement not made yet */
-
/* Deconstruct fdw_private data. */
! fmstate->query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! fmstate->target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! fmstate->has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! fmstate->retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
!
! /* Create context for per-tuple temp workspace. */
! fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
! "postgres_fdw temporary data",
! ALLOCSET_SMALL_SIZES);
!
! /* Prepare for input conversion of RETURNING results. */
! if (fmstate->has_returning)
! fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
!
! /* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 1;
! fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
! fmstate->p_nums = 0;
!
! if (operation == CMD_UPDATE || operation == CMD_DELETE)
! {
! /* Find the ctid resjunk column in the subplan's result */
! Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
!
! fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
! "ctid");
! if (!AttributeNumberIsValid(fmstate->ctidAttno))
! elog(ERROR, "could not find junk ctid column");
! /* First transmittable parameter will be ctid */
! getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
!
! if (operation == CMD_INSERT || operation == CMD_UPDATE)
! {
! /* Set up for remaining transmittable parameters */
! foreach(lc, fmstate->target_attrs)
! {
! int attnum = lfirst_int(lc);
! Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
!
! Assert(!attr->attisdropped);
!
! getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
! }
!
! Assert(fmstate->p_nums <= n_params);
resultRelInfo->ri_FdwState = fmstate;
}
--- 1702,1726 ----
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/* Deconstruct fdw_private data. */
! query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
! /* Construct an execution state. */
! fmstate = create_foreign_modify(mtstate->ps.state,
! resultRelInfo,
! mtstate->operation,
! mtstate->mt_plans[subplan_index]->plan,
! query,
! target_attrs,
! has_returning,
! retrieved_attrs);
resultRelInfo->ri_FdwState = fmstate;
}
***************
*** 2011,2038 **** postgresEndForeignModify(EState *estate,
if (fmstate == NULL)
return;
! /* If we created a prepared statement, destroy it */
! if (fmstate->p_name)
! {
! char sql[64];
! PGresult *res;
!
! snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
!
! /*
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_exec_query(fmstate->conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
! PQclear(res);
! fmstate->p_name = NULL;
! }
!
! /* Release remote connection */
! ReleaseConnection(fmstate->conn);
! fmstate->conn = NULL;
}
/*
--- 1955,1962 ----
if (fmstate == NULL)
return;
! /* Destroy the execution state */
! finish_foreign_modify(fmstate);
}
/*
***************
*** 3229,3234 **** close_cursor(PGconn *conn, unsigned int cursor_number)
--- 3153,3261 ----
}
/*
+ * create_foreign_modify
+ * Construct an execution state of a foreign insert/update/delete
+ * operation
+ */
+ static PgFdwModifyState *
+ create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs)
+ {
+ PgFdwModifyState *fmstate;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ RangeTblEntry *rte;
+ Oid userid;
+ ForeignTable *table;
+ UserMapping *user;
+ AttrNumber n_params;
+ Oid typefnoid;
+ bool isvarlena;
+ ListCell *lc;
+
+ /* Begin constructing PgFdwModifyState. */
+ fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
+ fmstate->rel = rel;
+
+ /*
+ * Identify which user to do the remote access as. This should match what
+ * ExecCheckRTEPerms() does.
+ */
+ rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ /* Get info about foreign table. */
+ table = GetForeignTable(RelationGetRelid(rel));
+ user = GetUserMapping(userid, table->serverid);
+
+ /* Open connection; report that we'll create a prepared statement. */
+ fmstate->conn = GetConnection(user, true);
+ fmstate->p_name = NULL; /* prepared statement not made yet */
+
+ /* Set up remote query information. */
+ fmstate->query = query;
+ fmstate->target_attrs = target_attrs;
+ fmstate->has_returning = has_returning;
+ fmstate->retrieved_attrs = retrieved_attrs;
+
+ /* Create context for per-tuple temp workspace. */
+ fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+ "postgres_fdw temporary data",
+ ALLOCSET_SMALL_SIZES);
+
+ /* Prepare for input conversion of RETURNING results. */
+ if (fmstate->has_returning)
+ fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ /* Prepare for output conversion of parameters used in prepared stmt. */
+ n_params = list_length(fmstate->target_attrs) + 1;
+ fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
+ fmstate->p_nums = 0;
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ Assert(subplan != NULL);
+
+ /* Find the ctid resjunk column in the subplan's result */
+ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "ctid");
+ if (!AttributeNumberIsValid(fmstate->ctidAttno))
+ elog(ERROR, "could not find junk ctid column");
+
+ /* First transmittable parameter will be ctid */
+ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ /* Set up for remaining transmittable parameters */
+ foreach(lc, fmstate->target_attrs)
+ {
+ int attnum = lfirst_int(lc);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ Assert(!attr->attisdropped);
+
+ getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+ }
+
+ Assert(fmstate->p_nums <= n_params);
+
+ return fmstate;
+ }
+
+ /*
* prepare_foreign_modify
* Establish a prepared statement for execution of INSERT/UPDATE/DELETE
*/
***************
*** 3371,3376 **** store_returning_result(PgFdwModifyState *fmstate,
--- 3398,3436 ----
}
/*
+ * finish_foreign_modify
+ * Release resources for a foreign insert/update/delete operation
+ */
+ static void
+ finish_foreign_modify(PgFdwModifyState *fmstate)
+ {
+ Assert(fmstate != NULL);
+
+ /* If we created a prepared statement, destroy it */
+ if (fmstate->p_name)
+ {
+ char sql[64];
+ PGresult *res;
+
+ snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
+
+ /*
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_exec_query(fmstate->conn, sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
+ PQclear(res);
+ fmstate->p_name = NULL;
+ }
+
+ /* Release remote connection */
+ ReleaseConnection(fmstate->conn);
+ fmstate->conn = NULL;
+ }
+
+ /*
* build_remote_returning
* Build a RETURNING targetlist of a remote query for performing an
* UPDATE/DELETE .. RETURNING on a join directly
foreign-routing-fdwapi-5.patchtext/x-diff; name=foreign-routing-fdwapi-5.patchDownload
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 136,141 **** DELETE FROM agg_csv WHERE a = 100;
--- 136,146 ----
-- but this should be allowed
SELECT * FROM agg_csv FOR UPDATE;
+ -- copy from isn't supported either
+ COPY agg_csv FROM STDIN;
+ 12 3.4
+ \.
+
-- constraint exclusion tests
\t on
EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 221,226 **** SELECT * FROM agg_csv FOR UPDATE;
--- 221,229 ----
42 | 324.78
(3 rows)
+ -- copy from isn't supported either
+ COPY agg_csv FROM STDIN;
+ ERROR: cannot insert into foreign table "agg_csv"
-- constraint exclusion tests
\t on
EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
***************
*** 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;
--- 318,324 ----
(0 rows)
COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
! ERROR: cannot insert into foreign table "p1"
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
***************
*** 342,351 **** SELECT tableoid::regclass, * FROM p2;
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
--- 345,354 ----
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot insert into foreign table "p1"
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot insert into foreign table "p1"
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7371,7376 **** NOTICE: drop cascades to foreign table bar2
--- 7371,7710 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ -- Test insert tuple routing
+ create table itrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+ insert into itrtest values (1, 'foo');
+ insert into itrtest values (1, 'bar') returning *;
+ a | b
+ ---+-----
+ 1 | bar
+ (1 row)
+
+ insert into itrtest values (2, 'baz');
+ insert into itrtest values (2, 'qux') returning *;
+ a | b
+ ---+-----
+ 2 | qux
+ (1 row)
+
+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
+ a | b
+ ---+-------
+ 1 | test1
+ 2 | test2
+ (2 rows)
+
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b
+ ----------+---+-------
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ remp1 | 1 | test1
+ remp2 | 2 | baz
+ remp2 | 2 | qux
+ remp2 | 2 | test2
+ (6 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-------
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ remp1 | 1 | test1
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | a
+ ----------+-------+---
+ remp2 | baz | 2
+ remp2 | qux | 2
+ remp2 | test2 | 2
+ (3 rows)
+
+ delete from itrtest;
+ create unique index loct1_idx on loct1 (a);
+ -- DO NOTHING without an inference specification is supported
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ a | b
+ ---+-----
+ 1 | foo
+ (1 row)
+
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ a | b
+ ---+---
+ (0 rows)
+
+ -- But other cases are not supported
+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ (1 row)
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+ -- Test update tuple routing
+ create table utrtest (a int, b text) partition by list (a);
+ create table loct (a int check (a in (1)), b text);
+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+ insert into utrtest values (1, 'foo');
+ insert into utrtest values (2, 'qux');
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ locp | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+-----
+ locp | 2 | qux
+ (1 row)
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where b = 'foo' returning *;
+ ERROR: new row for relation "loct" violates check constraint "loct_a_check"
+ DETAIL: Failing row contains (2, foo).
+ CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
+ -- But the reverse is allowed
+ update utrtest set a = 1 where b = 'qux' returning *;
+ a | b
+ ---+-----
+ 1 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ remp | 1 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ remp | 1 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ (0 rows)
+
+ -- The executor should not let unexercised FDWs shut down
+ update utrtest set a = 1 where b = 'foo';
+ drop table utrtest;
+ drop table loct;
+ -- Test copy tuple routing
+ create table ctrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+ copy ctrtest from stdin;
+ select tableoid::regclass, * FROM ctrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ remp2 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | a
+ ----------+-----+---
+ remp2 | qux | 2
+ (1 row)
+
+ -- Copying into foreign partitions directly should work as well
+ copy remp1 from stdin;
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ (2 rows)
+
+ drop table ctrtest;
+ drop table loct1;
+ drop table loct2;
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+ -- Test basic functionality
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ delete from rem2;
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+ -- check constraint is enforced on the remote side, not locally
+ copy rem2 from stdin;
+ copy rem2 from stdin; -- ERROR
+ ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive"
+ DETAIL: Failing row contains (-1, xyzzy).
+ CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2)
+ COPY rem2, line 1: "-1 xyzzy"
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+ delete from rem2;
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ copy rem2 from stdin;
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+ delete from rem2;
+ create trigger trig_row_before_insert before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insert on rem2;
+ delete from rem2;
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on rem2;
+ delete from rem2;
+ -- Test remote triggers
+ create trigger trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insert on loc2;
+ delete from rem2;
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on loc2;
+ delete from rem2;
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ copy rem2 from stdin;
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,"foo triggered !")
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,"bar triggered !")
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insert on loc2;
+ delete from rem2;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 319,324 **** static TupleTableSlot *postgresExecForeignDelete(EState *estate,
--- 319,328 ----
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
+ static void postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo);
+ static void postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo);
static int postgresIsForeignRelUpdatable(Relation rel);
static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
***************
*** 473,478 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 477,484 ----
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
+ routine->BeginForeignInsert = postgresBeginForeignInsert;
+ routine->EndForeignInsert = postgresEndForeignInsert;
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
routine->PlanDirectModify = postgresPlanDirectModify;
routine->BeginDirectModify = postgresBeginDirectModify;
***************
*** 1960,1965 **** postgresEndForeignModify(EState *estate,
--- 1966,2061 ----
}
/*
+ * postgresBeginForeignInsert
+ * Begin an insert operation on a foreign table
+ */
+ static void
+ postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate;
+ Plan *plan = mtstate->ps.plan;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Query *query;
+ PlannerInfo *root;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int attnum;
+ StringInfoData sql;
+ List *targetAttrs = NIL;
+ List *retrieved_attrs = NIL;
+ bool doNothing = false;
+
+ initStringInfo(&sql);
+
+ /* Set up largely-dummy planner state. */
+ rte = makeNode(RangeTblEntry);
+ rte->rtekind = RTE_RELATION;
+ rte->relid = RelationGetRelid(rel);
+ rte->relkind = RELKIND_FOREIGN_TABLE;
+ query = makeNode(Query);
+ query->commandType = CMD_INSERT;
+ query->resultRelation = 1;
+ query->rtable = list_make1(rte);
+ root = makeNode(PlannerInfo);
+ root->parse = query;
+
+ /* We transmit all columns that are defined in the foreign table. */
+ for (attnum = 1; attnum <= tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (!attr->attisdropped)
+ targetAttrs = lappend_int(targetAttrs, attnum);
+ }
+
+ /* Check if we add the ON CONFLICT clause to the remote query. */
+ if (plan)
+ {
+ OnConflictAction onConflictAction = ((ModifyTable *) plan)->onConflictAction;
+
+ /* We only support DO NOTHING without an inference specification. */
+ if (onConflictAction == ONCONFLICT_NOTHING)
+ doNothing = true;
+ else if (onConflictAction != ONCONFLICT_NONE)
+ elog(ERROR, "unexpected ON CONFLICT specification: %d",
+ (int) onConflictAction);
+ }
+
+ /* Construct the SQL command string. */
+ deparseInsertSql(&sql, root, 1, rel, targetAttrs, doNothing,
+ resultRelInfo->ri_returningList, &retrieved_attrs);
+
+ /* Construct an execution state. */
+ fmstate = create_foreign_modify(mtstate->ps.state,
+ resultRelInfo,
+ CMD_INSERT,
+ NULL,
+ sql.data,
+ targetAttrs,
+ retrieved_attrs != NIL,
+ retrieved_attrs);
+
+ resultRelInfo->ri_FdwState = fmstate;
+ }
+
+ /*
+ * postgresEndForeignInsert
+ * Finish an insert operation on a foreign table
+ */
+ static void
+ postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+
+ Assert(fmstate != NULL);
+
+ /* Destroy the execution state */
+ finish_foreign_modify(fmstate);
+ }
+
+ /*
* postgresIsForeignRelUpdatable
* Determine whether a foreign table supports INSERT, UPDATE and/or
* DELETE.
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1768,1773 **** drop table loct1;
--- 1768,2010 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ -- Test insert tuple routing
+ create table itrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+
+ insert into itrtest values (1, 'foo');
+ insert into itrtest values (1, 'bar') returning *;
+ insert into itrtest values (2, 'baz');
+ insert into itrtest values (2, 'qux') returning *;
+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
+
+ select tableoid::regclass, * FROM itrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ delete from itrtest;
+
+ create unique index loct1_idx on loct1 (a);
+
+ -- DO NOTHING without an inference specification is supported
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+
+ -- But other cases are not supported
+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
+
+ select tableoid::regclass, * FROM itrtest;
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test update tuple routing
+ create table utrtest (a int, b text) partition by list (a);
+ create table loct (a int check (a in (1)), b text);
+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+
+ insert into utrtest values (1, 'foo');
+ insert into utrtest values (2, 'qux');
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where b = 'foo' returning *;
+
+ -- But the reverse is allowed
+ update utrtest set a = 1 where b = 'qux' returning *;
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- The executor should not let unexercised FDWs shut down
+ update utrtest set a = 1 where b = 'foo';
+
+ drop table utrtest;
+ drop table loct;
+
+ -- Test copy tuple routing
+ create table ctrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+
+ copy ctrtest from stdin;
+ 1 foo
+ 2 qux
+ \.
+
+ select tableoid::regclass, * FROM ctrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ -- Copying into foreign partitions directly should work as well
+ copy remp1 from stdin;
+ 1 bar
+ \.
+
+ select tableoid::regclass, * FROM remp1;
+
+ drop table ctrtest;
+ drop table loct1;
+ drop table loct2;
+
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+
+ -- Test basic functionality
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ delete from rem2;
+
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+
+ -- check constraint is enforced on the remote side, not locally
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ copy rem2 from stdin; -- ERROR
+ -1 xyzzy
+ \.
+ select * from rem2;
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+
+ delete from rem2;
+
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+
+ delete from rem2;
+
+ create trigger trig_row_before_insert before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insert on rem2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on rem2;
+
+ delete from rem2;
+
+ -- Test remote triggers
+ create trigger trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insert on loc2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on loc2;
+
+ delete from rem2;
+
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insert on loc2;
+
+ delete from rem2;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 3037,3047 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<para>
--- 3037,3045 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<para>
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 695,700 **** EndForeignModify(EState *estate,
--- 695,766 ----
</para>
<para>
+ Tuples inserted into a partitioned table by <command>INSERT</command> or
+ <command>COPY FROM</command> are routed to partitions. If an FDW
+ supports routable foreign-table partitions, it should also provide the
+ following callback functions. These functions are also called when
+ <command>COPY FROM</command> is executed on a foreign table.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ BeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ Begin executing an insert operation on a foreign table. This routine is
+ called right before the first tuple is inserted into the foreign table
+ in both cases when it is the partition chosen for tuple routing and the
+ target specified in a <command>COPY FROM</command> command. It should
+ perform any initialization needed prior to the actual insertion.
+ Subsequently, <function>ExecForeignInsert</function> will be called for
+ each tuple to be inserted into the foreign table.
+ </para>
+
+ <para>
+ <literal>mtstate</literal> is the overall state of the
+ <structname>ModifyTable</structname> plan node being executed; global data about
+ the plan and execution state is available via this structure.
+ <literal>rinfo</literal> is the <structname>ResultRelInfo</structname> struct describing
+ the target foreign table. (The <structfield>ri_FdwState</structfield> field of
+ <structname>ResultRelInfo</structname> is available for the FDW to store any
+ private state it needs for this operation.)
+ </para>
+
+ <para>
+ When this is called by a <command>COPY FROM</command> command, the
+ plan-related global data in <literal>mtstate</literal> is not provided
+ and the <literal>planSlot</literal> parameter of
+ <function>ExecForeignInsert</function> subsequently called for each
+ inserted tuple is <literal>NULL</literal>, whether the foreign table is
+ the partition chosen for tuple routing or the target specified in the
+ command.
+ </para>
+
+ <para>
+ If the <function>BeginForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the initialization.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ EndForeignInsert(EState *estate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ End the insert operation and release resources. It is normally not important
+ to release palloc'd memory, but for example open files and connections
+ to remote servers should be cleaned up.
+ </para>
+
+ <para>
+ If the <function>EndForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the termination.
+ </para>
+
+ <para>
<programlisting>
int
IsForeignRelUpdatable(Relation rel);
*** a/doc/src/sgml/ref/copy.sgml
--- b/doc/src/sgml/ref/copy.sgml
***************
*** 402,409 **** COPY <replaceable class="parameter">count</replaceable>
</para>
<para>
! <command>COPY FROM</command> can be used with plain tables and with views
! that have <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
--- 402,410 ----
</para>
<para>
! <command>COPY FROM</command> can be used with plain, foreign, or
! partitioned tables or with views that have
! <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 291,296 **** UPDATE <replaceable class="parameter">count</replaceable>
--- 291,299 ----
concurrent <command>UPDATE</command> or <command>DELETE</command> on the
same row may miss this row. For details see the section
<xref linkend="ddl-partitioning-declarative-limitations"/>.
+ Currently, it is not allowed to move a row from a partition that is a
+ foreign table to another, but the reverse is allowed if the foreign table
+ is routable.
</para>
</refsect1>
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 29,34 ****
--- 29,35 ----
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
***************
*** 2284,2289 **** CopyFrom(CopyState cstate)
--- 2285,2291 ----
ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
+ ModifyTableState *mtstate;
ExprContext *econtext;
TupleTableSlot *myslot;
MemoryContext oldcontext = CurrentMemoryContext;
***************
*** 2305,2315 **** CopyFrom(CopyState cstate)
Assert(cstate->rel);
/*
! * The target must be a plain relation or have an INSTEAD OF INSERT row
! * trigger. (Currently, such triggers are only allowed on views, so we
! * only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
--- 2307,2318 ----
Assert(cstate->rel);
/*
! * The target must be a plain, foreign, or partitioned relation, or have
! * an INSTEAD OF INSERT row trigger. (Currently, such triggers are only
! * allowed on views, so we only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
***************
*** 2325,2335 **** CopyFrom(CopyState cstate)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy to materialized view \"%s\"",
RelationGetRelationName(cstate->rel))));
- else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy to foreign table \"%s\"",
- RelationGetRelationName(cstate->rel))));
else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
--- 2328,2333 ----
***************
*** 2436,2441 **** CopyFrom(CopyState cstate)
--- 2434,2442 ----
NULL,
0);
+ /* Verify the named relation is a valid target for INSERT */
+ CheckValidResultRel(resultRelInfo, CMD_INSERT);
+
ExecOpenIndices(resultRelInfo, false);
estate->es_result_relations = resultRelInfo;
***************
*** 2448,2453 **** CopyFrom(CopyState cstate)
--- 2449,2469 ----
/* Triggers might need a slot as well */
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+ /*
+ * Set up a ModifyTableState so we can let FDW(s) init themselves for
+ * foreign-table result relation(s).
+ */
+ mtstate = makeNode(ModifyTableState);
+ mtstate->ps.plan = NULL;
+ mtstate->ps.state = estate;
+ mtstate->operation = CMD_INSERT;
+ mtstate->resultRelInfo = estate->es_result_relations;
+
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate,
+ resultRelInfo);
+
/* Prepare to catch AFTER triggers. */
AfterTriggerBeginQuery();
***************
*** 2489,2499 **** CopyFrom(CopyState cstate)
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
--- 2505,2516 ----
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is foreign or partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ resultRelInfo->ri_FdwRoutine != NULL ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
***************
*** 2608,2626 **** CopyFrom(CopyState cstate)
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
! resultRelInfo = ExecInitPartitionInfo(NULL,
saved_resultRelInfo,
proute, estate,
leaf_part_index);
Assert(resultRelInfo != NULL);
}
- /* 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
*/
--- 2625,2637 ----
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
! resultRelInfo = ExecInitPartitionInfo(mtstate,
saved_resultRelInfo,
proute, estate,
leaf_part_index);
Assert(resultRelInfo != NULL);
}
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
*/
***************
*** 2708,2716 **** CopyFrom(CopyState cstate)
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /* Check the constraints of the tuple */
! if (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
--- 2719,2731 ----
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /*
! * If the target is a plain table, check the constraints of
! * the tuple.
! */
! if (resultRelInfo->ri_FdwRoutine == NULL &&
! (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr))
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
***************
*** 2742,2751 **** CopyFrom(CopyState cstate)
{
List *recheckIndexes = NIL;
! /* OK, store the tuple and create index entries for it */
! heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
! hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
--- 2757,2788 ----
{
List *recheckIndexes = NIL;
! /* OK, store the tuple */
! if (resultRelInfo->ri_FdwRoutine != NULL)
! {
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! NULL);
!
! if (slot == NULL) /* "do nothing" */
! goto next_tuple;
!
! /* FDW might have changed tuple */
! tuple = ExecMaterializeSlot(slot);
+ /*
+ * AFTER ROW Triggers might reference the tableoid
+ * column, so initialize t_tableOid before evaluating
+ * them.
+ */
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+ else
+ heap_insert(resultRelInfo->ri_RelationDesc, tuple,
+ mycid, hi_options, bistate);
+
+ /* And create index entries for it */
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
***************
*** 2763,2775 **** CopyFrom(CopyState cstate)
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger;
! * this is the same definition used by execMain.c for counting
! * tuples inserted by an INSERT command.
*/
processed++;
}
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
--- 2800,2813 ----
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger
! * or FDW; this is the same definition used by nodeModifyTable.c
! * for counting tuples inserted by an INSERT command.
*/
processed++;
}
+ next_tuple:
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
***************
*** 2810,2820 **** CopyFrom(CopyState cstate)
ExecResetTupleTable(estate->es_tupleTable, false);
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
--- 2848,2864 ----
ExecResetTupleTable(estate->es_tupleTable, false);
+ /* Allow the FDW to shut down */
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(estate,
+ resultRelInfo);
+
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(mtstate, cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1179,1191 **** 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),
--- 1179,1184 ----
***************
*** 1378,1383 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1371,1377 ----
resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionReadyForRouting = false;
}
/*
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 18,23 ****
--- 18,24 ----
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
***************
*** 55,66 **** static List *adjust_partition_tlist(List *tlist, TupleConversionMap *map);
* see ExecInitPartitionInfo. However, if the function is invoked for update
* tuple routing, caller would already have initialized ResultRelInfo's for
* some of the partitions, which are reused and assigned to their respective
! * slot in the aforementioned array.
*/
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
- TupleDesc tupDesc = RelationGetDescr(rel);
List *leaf_parts;
ListCell *cell;
int i;
--- 56,68 ----
* see ExecInitPartitionInfo. However, if the function is invoked for update
* tuple routing, caller would already have initialized ResultRelInfo's for
* some of the partitions, which are reused and assigned to their respective
! * slot in the aforementioned array. For such partitions, we delay setting
! * up objects such as TupleConversionMap until those are actually chosen as
! * the partitions to route tuples to. See ExecPrepareTupleRouting.
*/
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
List *leaf_parts;
ListCell *cell;
int i;
***************
*** 141,151 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
if (update_rri_index < num_update_rri &&
RelationGetRelid(update_rri[update_rri_index].ri_RelationDesc) == leaf_oid)
{
- Relation partrel;
- TupleDesc part_tupdesc;
-
leaf_part_rri = &update_rri[update_rri_index];
- partrel = leaf_part_rri->ri_RelationDesc;
/*
* This is required in order to convert the partition's tuple to
--- 143,149 ----
***************
*** 159,181 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
proute->subplan_partition_offsets[update_rri_index] = i;
update_rri_index++;
-
- 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.
- */
- proute->parent_child_tupconv_maps[i] =
- convert_tuples_by_name(tupDesc, part_tupdesc,
- gettext_noop("could not convert row type"));
-
- /*
- * Verify result relation is a valid target for an INSERT. An
- * UPDATE of a partition-key becomes a DELETE+INSERT operation, so
- * this check is required even when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
}
proute->partitions[i] = leaf_part_rri;
--- 157,162 ----
***************
*** 342,351 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
PartitionTupleRouting *proute,
EState *estate, int partidx)
{
Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel;
ResultRelInfo *leaf_part_rri;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
MemoryContext oldContext;
/*
--- 323,332 ----
PartitionTupleRouting *proute,
EState *estate, int partidx)
{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel;
ResultRelInfo *leaf_part_rri;
MemoryContext oldContext;
/*
***************
*** 369,379 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
leaf_part_rri->ri_PartitionLeafIndex = partidx;
! /*
! * Verify result relation is a valid target for an INSERT. An UPDATE of a
! * partition-key becomes a DELETE+INSERT operation, so this check is still
! * required when the operation is CMD_UPDATE.
! */
CheckValidResultRel(leaf_part_rri, CMD_INSERT);
/*
--- 350,356 ----
leaf_part_rri->ri_PartitionLeafIndex = partidx;
! /* Verify the specified partition is a valid target for INSERT */
CheckValidResultRel(leaf_part_rri, CMD_INSERT);
/*
***************
*** 389,394 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 366,376 ----
leaf_part_rri);
/*
+ * Set up information needed for routing tuples to the specified partition
+ */
+ ExecInitRoutingInfo(mtstate, estate, proute, leaf_part_rri, partidx);
+
+ /*
* Open partition indices. The user may have asked to check for conflicts
* within this leaf partition and do "nothing" instead of throwing an
* error. Be prepared in that case by initializing the index information
***************
*** 493,498 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 475,481 ----
returningList = map_partition_varattnos(returningList, firstVarno,
partrel, firstResultRel,
NULL);
+ leaf_part_rri->ri_returningList = returningList;
/*
* Initialize the projection itself.
***************
*** 510,524 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
}
/*
- * Save a tuple conversion map to convert a tuple routed to this partition
- * from the parent's type to the partition's.
- */
- proute->parent_child_tupconv_maps[partidx] =
- convert_tuples_by_name(RelationGetDescr(rootrel),
- RelationGetDescr(partrel),
- gettext_noop("could not convert row type"));
-
- /*
* If there is an ON CONFLICT clause, initialize state for it.
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
--- 493,498 ----
***************
*** 654,659 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 628,635 ----
}
}
+ leaf_part_rri->ri_PartitionReadyForRouting = true;
+
Assert(proute->partitions[partidx] == NULL);
proute->partitions[partidx] = leaf_part_rri;
***************
*** 747,752 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 723,766 ----
}
/*
+ * ExecInitRoutingInfo
+ * Set up information needed for routing tuples to a leaf partition
+ */
+ void
+ ExecInitRoutingInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx)
+ {
+ MemoryContext oldContext;
+
+ /*
+ * Switch into per-query memory context.
+ */
+ oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Set up a tuple conversion map to convert a tuple routed to the
+ * partition from the parent's type to the partition's.
+ */
+ proute->parent_child_tupconv_maps[partidx] =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
+ RelationGetDescr(partRelInfo->ri_RelationDesc),
+ gettext_noop("could not convert row type"));
+
+ /*
+ * If the partition is a foreign table, let the FDW init itself for
+ * routing tuples to the partition.
+ */
+ if (partRelInfo->ri_FdwRoutine != NULL &&
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
+
+ MemoryContextSwitchTo(oldContext);
+ }
+
+ /*
* ExecSetupChildParentMapForLeaf -- Initialize the per-leaf-partition
* child-to-root tuple conversion map array.
*
***************
*** 848,854 **** ConvertPartitionTupleSlot(TupleConversionMap *map,
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
--- 862,869 ----
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
***************
*** 876,881 **** ExecCleanupTupleRouting(PartitionTupleRouting *proute)
--- 891,903 ----
if (resultRelInfo == NULL)
continue;
+ /* Allow any FDWs to shut down if they've been exercised */
+ if (resultRelInfo->ri_PartitionReadyForRouting &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(mtstate->ps.state,
+ resultRelInfo);
+
/*
* If this result rel is one of the UPDATE subplan result rels, let
* ExecEndPlan() close it. For INSERT or COPY,
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 1831,1841 **** ExecPrepareTupleRouting(ModifyTableState *mtstate,
proute, estate,
partidx);
! /* We do not yet have a way to insert into a foreign partition */
! if (partrel->ri_FdwRoutine)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/*
* Make it look like we are inserting into the partition.
--- 1831,1856 ----
proute, estate,
partidx);
! /*
! * Verify the partition is a valid target for INSERT if we didn't yet.
! *
! * Note: an UPDATE of a partition key invokes an INSERT that moves the
! * tuple to a new partition. This check would be applied to a subplan
! * partition of such an UPDATE that is chosen as the partition to move
! * the tuple to. The reason we do this check here rather than in
! * ExecSetupPartitionTupleRouting is to avoid aborting such an UPDATE
! * unnecessarily due to non-routable subplan partitions that may not be
! * chosen for update tuple movement after all.
! */
! if (!partrel->ri_PartitionReadyForRouting)
! {
! CheckValidResultRel(partrel, CMD_INSERT);
!
! /* Set up information needed for routing tuples to the partition */
! ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
!
! partrel->ri_PartitionReadyForRouting = true;
! }
/*
* Make it look like we are inserting into the partition.
***************
*** 2536,2541 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 2551,2557 ----
{
List *rlist = (List *) lfirst(l);
+ resultRelInfo->ri_returningList = rlist;
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
***************
*** 2931,2937 **** ExecEndModifyTable(ModifyTableState *node)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node->mt_partition_tuple_routing);
/*
* Free the exprcontext
--- 2947,2953 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
/*
* Free the exprcontext
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 119,124 **** extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 119,129 ----
ResultRelInfo *resultRelInfo,
PartitionTupleRouting *proute,
EState *estate, int partidx);
+ extern void ExecInitRoutingInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx);
extern void ExecSetupChildParentMapForLeaf(PartitionTupleRouting *proute);
extern TupleConversionMap *TupConvMapForLeaf(PartitionTupleRouting *proute,
ResultRelInfo *rootRelInfo, int leaf_index);
***************
*** 126,131 **** extern HeapTuple ConvertPartitionTupleSlot(TupleConversionMap *map,
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
--- 131,137 ----
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 98,103 **** typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
--- 98,109 ----
typedef void (*EndForeignModify_function) (EState *estate,
ResultRelInfo *rinfo);
+ typedef void (*BeginForeignInsert_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+
+ typedef void (*EndForeignInsert_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
typedef int (*IsForeignRelUpdatable_function) (Relation rel);
typedef bool (*PlanDirectModify_function) (PlannerInfo *root,
***************
*** 205,210 **** typedef struct FdwRoutine
--- 211,218 ----
ExecForeignUpdate_function ExecForeignUpdate;
ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify;
+ BeginForeignInsert_function BeginForeignInsert;
+ EndForeignInsert_function EndForeignInsert;
IsForeignRelUpdatable_function IsForeignRelUpdatable;
PlanDirectModify_function PlanDirectModify;
BeginDirectModify_function BeginDirectModify;
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 444,449 **** typedef struct ResultRelInfo
--- 444,452 ----
/* for removing junk attributes from tuples */
JunkFilter *ri_junkFilter;
+ /* list of RETURNING expressions */
+ List *ri_returningList;
+
/* for computing a RETURNING list */
ProjectionInfo *ri_projectReturning;
***************
*** 462,467 **** typedef struct ResultRelInfo
--- 465,473 ----
/* relation descriptor for root partitioned table */
Relation ri_PartitionRoot;
+ /* true if ready for tuple routing */
+ bool ri_PartitionReadyForRouting;
+
int ri_PartitionLeafIndex;
/* for running MERGE on this result relation */
MergeState *ri_mergeState;
Fujita-san,
On 2018/04/05 15:02, Etsuro Fujita wrote:
(2018/04/04 19:31), Etsuro Fujita wrote:
Attached is an updated version of the patch set:
* As before, patch foreign-routing-fdwapi-4.patch is created on top of
patch postgres-fdw-refactoring-4.patch and the bug-fix patch [1].I did a bit of cleanup and comment-rewording to patch
foreign-routing-fdwapi-4.patch. Attached is a new version of the patch
set. I renamed the postgres_fdw refactoring patch but no changes to that
patch.
I noticed that the 2nd patch (foreign-routing-fdwapi-5.patch) fails to
apply to copy.c:
Hunk #9 FAILED at 2719.
Hunk #10 succeeded at 2752 (offset -1 lines).
Hunk #11 succeeded at 2795 (offset -1 lines).
Hunk #12 succeeded at 2843 (offset -1 lines).
1 out of 12 hunks FAILED -- saving rejects to file
src/backend/commands/copy.c.rej
cat src/backend/commands/copy.c.rej
*** src/backend/commands/copy.c
--- src/backend/commands/copy.c
***************
*** 2719,2727 ****
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /* Check the constraints of the tuple */
! if (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
--- 2730,2742 ----
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /*
! * If the target is a plain table, check the constraints of
! * the tuple.
! */
! if (resultRelInfo->ri_FdwRoutine == NULL &&
! (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr))
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
Can you check?
Thanks,
Amit
(2018/04/05 15:37), Amit Langote wrote:
On 2018/04/05 15:02, Etsuro Fujita wrote:
(2018/04/04 19:31), Etsuro Fujita wrote:
Attached is an updated version of the patch set:
* As before, patch foreign-routing-fdwapi-4.patch is created on top of
patch postgres-fdw-refactoring-4.patch and the bug-fix patch [1].I did a bit of cleanup and comment-rewording to patch
foreign-routing-fdwapi-4.patch. Attached is a new version of the patch
set. I renamed the postgres_fdw refactoring patch but no changes to that
patch.I noticed that the 2nd patch (foreign-routing-fdwapi-5.patch) fails to
apply to copy.c:Hunk #9 FAILED at 2719.
Hunk #10 succeeded at 2752 (offset -1 lines).
Hunk #11 succeeded at 2795 (offset -1 lines).
Hunk #12 succeeded at 2843 (offset -1 lines).
1 out of 12 hunks FAILED -- saving rejects to file
src/backend/commands/copy.c.rejcat src/backend/commands/copy.c.rej *** src/backend/commands/copy.c --- src/backend/commands/copy.c *************** *** 2719,2727 **** resultRelInfo->ri_TrigDesc->trig_insert_before_row)) check_partition_constr = false;! /* Check the constraints of the tuple */
! if (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);if (useHeapMultiInsert) --- 2730,2742 ---- resultRelInfo->ri_TrigDesc->trig_insert_before_row)) check_partition_constr = false;! /*
! * If the target is a plain table, check the constraints of
! * the tuple.
! */
! if (resultRelInfo->ri_FdwRoutine == NULL&&
! (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr))
ExecConstraints(resultRelInfo, slot, estate, true);if (useHeapMultiInsert)
Can you check?
I forgot to mention this: the second patch is created on top of the
first patch (postgres-fdw-refactoring-5.patch) and the patch in [1]/messages/by-id/5ABA4074.1090500@lab.ntt.co.jp as
before.
Thanks for reviewing, Amit!
Best regards,
Etsuro Fujita
Fuiita-san,
On 2018/04/05 15:56, Etsuro Fujita wrote:
(2018/04/05 15:37), Amit Langote wrote:
I noticed that the 2nd patch (foreign-routing-fdwapi-5.patch) fails to
apply to copy.c:I forgot to mention this: the second patch is created on top of the first
patch (postgres-fdw-refactoring-5.patch) and the patch in [1] as before.
Ah, sorry I hadn't noticed that in your previous email.
Might be a good idea to attach the bug-fix patch here as well, and perhaps
add numbers to the file names like:
0001_postgres-fdw-refactoring-5.patch
0002_BUGFIX-copy-from-check-constraint-fix.patch
0003_foreign-routing-fdwapi-5.patch
Just one minor comment:
I wonder why you decided not to have the CheckValidResultRel() call and
the statement that sets ri_PartitionReadyForRouting inside the newly added
ExecInitRoutingInfo itself. If ExecInitRoutingInfo does the last
necessary steps for a ResultRelInfo (and hence the partition) to be ready
to be used for routing, why not finish everything there. So the changes
to ExecPrepareTupleRouting which look like this in the patch:
+ if (!partrel->ri_PartitionReadyForRouting)
+ {
+ CheckValidResultRel(partrel, CMD_INSERT);
+
+ /* Set up information needed for routing tuples to the partition */
+ ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
+
+ partrel->ri_PartitionReadyForRouting = true;
+ }
will become:
+ if (!partrel->ri_PartitionReadyForRouting)
+ ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
As I see no other issues, I will mark this as Ready for Committer.
Thanks,
Amit
On 2018/04/05 16:31, Amit Langote wrote:
Fuiita-san,
Oops, sorry about misspelling your name here, Fujita-san.
- Amit
(2018/04/05 16:31), Amit Langote wrote:
Might be a good idea to attach the bug-fix patch here as well, and perhaps
add numbers to the file names like:0001_postgres-fdw-refactoring-5.patch
0002_BUGFIX-copy-from-check-constraint-fix.patch
0003_foreign-routing-fdwapi-5.patch
OK
Just one minor comment:
I wonder why you decided not to have the CheckValidResultRel() call and
the statement that sets ri_PartitionReadyForRouting inside the newly added
ExecInitRoutingInfo itself. If ExecInitRoutingInfo does the last
necessary steps for a ResultRelInfo (and hence the partition) to be ready
to be used for routing, why not finish everything there. So the changes
to ExecPrepareTupleRouting which look like this in the patch:+ if (!partrel->ri_PartitionReadyForRouting) + { + CheckValidResultRel(partrel, CMD_INSERT); + + /* Set up information needed for routing tuples to the partition */ + ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx); + + partrel->ri_PartitionReadyForRouting = true; + }will become:
+ if (!partrel->ri_PartitionReadyForRouting) + ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
Good idea! Modified that way.
As I see no other issues, I will mark this as Ready for Committer.
Thanks!
Attached is an updated version of the patch set plus the patch in [1]/messages/by-id/5ABA4074.1090500@lab.ntt.co.jp.
Patch 0003_foreign-routing-fdwapi-6.patch can be applied on top of patch
0001_postgres-fdw-refactoring-6.patch and
0002_copy-from-check-constraint-fix.patch.
Best regards,
Etsuro Fujita
Attachments:
0001_postgres-fdw-refactoring-6.patchtext/x-diff; name=0001_postgres-fdw-refactoring-6.patchDownload
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 376,387 **** static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
--- 376,396 ----
static void create_cursor(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
+ static PgFdwModifyState *create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs);
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
+ static void finish_foreign_modify(PgFdwModifyState *fmstate);
static List *build_remote_returning(Index rtindex, Relation rel,
List *returningList);
static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
***************
*** 1681,1698 **** postgresBeginForeignModify(ModifyTableState *mtstate,
int eflags)
{
PgFdwModifyState *fmstate;
! EState *estate = mtstate->ps.state;
! CmdType operation = mtstate->operation;
! Relation rel = resultRelInfo->ri_RelationDesc;
! RangeTblEntry *rte;
! Oid userid;
! ForeignTable *table;
! UserMapping *user;
! AttrNumber n_params;
! Oid typefnoid;
! bool isvarlena;
! ListCell *lc;
! TupleDesc tupdesc = RelationGetDescr(rel);
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
--- 1690,1699 ----
int eflags)
{
PgFdwModifyState *fmstate;
! char *query;
! List *target_attrs;
! bool has_returning;
! List *retrieved_attrs;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
***************
*** 1701,1782 **** postgresBeginForeignModify(ModifyTableState *mtstate,
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
- /* Begin constructing PgFdwModifyState. */
- fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
- fmstate->rel = rel;
-
- /*
- * Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
- */
- rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
- userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
- /* Get info about foreign table. */
- table = GetForeignTable(RelationGetRelid(rel));
- user = GetUserMapping(userid, table->serverid);
-
- /* Open connection; report that we'll create a prepared statement. */
- fmstate->conn = GetConnection(user, true);
- fmstate->p_name = NULL; /* prepared statement not made yet */
-
/* Deconstruct fdw_private data. */
! fmstate->query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! fmstate->target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! fmstate->has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! fmstate->retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
!
! /* Create context for per-tuple temp workspace. */
! fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
! "postgres_fdw temporary data",
! ALLOCSET_SMALL_SIZES);
!
! /* Prepare for input conversion of RETURNING results. */
! if (fmstate->has_returning)
! fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
!
! /* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 1;
! fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
! fmstate->p_nums = 0;
!
! if (operation == CMD_UPDATE || operation == CMD_DELETE)
! {
! /* Find the ctid resjunk column in the subplan's result */
! Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
!
! fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
! "ctid");
! if (!AttributeNumberIsValid(fmstate->ctidAttno))
! elog(ERROR, "could not find junk ctid column");
! /* First transmittable parameter will be ctid */
! getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
!
! if (operation == CMD_INSERT || operation == CMD_UPDATE)
! {
! /* Set up for remaining transmittable parameters */
! foreach(lc, fmstate->target_attrs)
! {
! int attnum = lfirst_int(lc);
! Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
!
! Assert(!attr->attisdropped);
!
! getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
! fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
! fmstate->p_nums++;
! }
! }
!
! Assert(fmstate->p_nums <= n_params);
resultRelInfo->ri_FdwState = fmstate;
}
--- 1702,1726 ----
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/* Deconstruct fdw_private data. */
! query = strVal(list_nth(fdw_private,
! FdwModifyPrivateUpdateSql));
! target_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateTargetAttnums);
! has_returning = intVal(list_nth(fdw_private,
! FdwModifyPrivateHasReturning));
! retrieved_attrs = (List *) list_nth(fdw_private,
! FdwModifyPrivateRetrievedAttrs);
! /* Construct an execution state. */
! fmstate = create_foreign_modify(mtstate->ps.state,
! resultRelInfo,
! mtstate->operation,
! mtstate->mt_plans[subplan_index]->plan,
! query,
! target_attrs,
! has_returning,
! retrieved_attrs);
resultRelInfo->ri_FdwState = fmstate;
}
***************
*** 2011,2038 **** postgresEndForeignModify(EState *estate,
if (fmstate == NULL)
return;
! /* If we created a prepared statement, destroy it */
! if (fmstate->p_name)
! {
! char sql[64];
! PGresult *res;
!
! snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
!
! /*
! * We don't use a PG_TRY block here, so be careful not to throw error
! * without releasing the PGresult.
! */
! res = pgfdw_exec_query(fmstate->conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
! PQclear(res);
! fmstate->p_name = NULL;
! }
!
! /* Release remote connection */
! ReleaseConnection(fmstate->conn);
! fmstate->conn = NULL;
}
/*
--- 1955,1962 ----
if (fmstate == NULL)
return;
! /* Destroy the execution state */
! finish_foreign_modify(fmstate);
}
/*
***************
*** 3229,3234 **** close_cursor(PGconn *conn, unsigned int cursor_number)
--- 3153,3261 ----
}
/*
+ * create_foreign_modify
+ * Construct an execution state of a foreign insert/update/delete
+ * operation
+ */
+ static PgFdwModifyState *
+ create_foreign_modify(EState *estate,
+ ResultRelInfo *resultRelInfo,
+ CmdType operation,
+ Plan *subplan,
+ char *query,
+ List *target_attrs,
+ bool has_returning,
+ List *retrieved_attrs)
+ {
+ PgFdwModifyState *fmstate;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ RangeTblEntry *rte;
+ Oid userid;
+ ForeignTable *table;
+ UserMapping *user;
+ AttrNumber n_params;
+ Oid typefnoid;
+ bool isvarlena;
+ ListCell *lc;
+
+ /* Begin constructing PgFdwModifyState. */
+ fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
+ fmstate->rel = rel;
+
+ /*
+ * Identify which user to do the remote access as. This should match what
+ * ExecCheckRTEPerms() does.
+ */
+ rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
+ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+ /* Get info about foreign table. */
+ table = GetForeignTable(RelationGetRelid(rel));
+ user = GetUserMapping(userid, table->serverid);
+
+ /* Open connection; report that we'll create a prepared statement. */
+ fmstate->conn = GetConnection(user, true);
+ fmstate->p_name = NULL; /* prepared statement not made yet */
+
+ /* Set up remote query information. */
+ fmstate->query = query;
+ fmstate->target_attrs = target_attrs;
+ fmstate->has_returning = has_returning;
+ fmstate->retrieved_attrs = retrieved_attrs;
+
+ /* Create context for per-tuple temp workspace. */
+ fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+ "postgres_fdw temporary data",
+ ALLOCSET_SMALL_SIZES);
+
+ /* Prepare for input conversion of RETURNING results. */
+ if (fmstate->has_returning)
+ fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ /* Prepare for output conversion of parameters used in prepared stmt. */
+ n_params = list_length(fmstate->target_attrs) + 1;
+ fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
+ fmstate->p_nums = 0;
+
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ Assert(subplan != NULL);
+
+ /* Find the ctid resjunk column in the subplan's result */
+ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "ctid");
+ if (!AttributeNumberIsValid(fmstate->ctidAttno))
+ elog(ERROR, "could not find junk ctid column");
+
+ /* First transmittable parameter will be ctid */
+ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+
+ if (operation == CMD_INSERT || operation == CMD_UPDATE)
+ {
+ /* Set up for remaining transmittable parameters */
+ foreach(lc, fmstate->target_attrs)
+ {
+ int attnum = lfirst_int(lc);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ Assert(!attr->attisdropped);
+
+ getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
+ }
+ }
+
+ Assert(fmstate->p_nums <= n_params);
+
+ return fmstate;
+ }
+
+ /*
* prepare_foreign_modify
* Establish a prepared statement for execution of INSERT/UPDATE/DELETE
*/
***************
*** 3371,3376 **** store_returning_result(PgFdwModifyState *fmstate,
--- 3398,3436 ----
}
/*
+ * finish_foreign_modify
+ * Release resources for a foreign insert/update/delete operation
+ */
+ static void
+ finish_foreign_modify(PgFdwModifyState *fmstate)
+ {
+ Assert(fmstate != NULL);
+
+ /* If we created a prepared statement, destroy it */
+ if (fmstate->p_name)
+ {
+ char sql[64];
+ PGresult *res;
+
+ snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
+
+ /*
+ * We don't use a PG_TRY block here, so be careful not to throw error
+ * without releasing the PGresult.
+ */
+ res = pgfdw_exec_query(fmstate->conn, sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
+ PQclear(res);
+ fmstate->p_name = NULL;
+ }
+
+ /* Release remote connection */
+ ReleaseConnection(fmstate->conn);
+ fmstate->conn = NULL;
+ }
+
+ /*
* build_remote_returning
* Build a RETURNING targetlist of a remote query for performing an
* UPDATE/DELETE .. RETURNING on a join directly
0003_foreign-routing-fdwapi-6.patchtext/x-diff; name=0003_foreign-routing-fdwapi-6.patchDownload
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 136,141 **** DELETE FROM agg_csv WHERE a = 100;
--- 136,146 ----
-- but this should be allowed
SELECT * FROM agg_csv FOR UPDATE;
+ -- copy from isn't supported either
+ COPY agg_csv FROM STDIN;
+ 12 3.4
+ \.
+
-- constraint exclusion tests
\t on
EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 221,226 **** SELECT * FROM agg_csv FOR UPDATE;
--- 221,229 ----
42 | 324.78
(3 rows)
+ -- copy from isn't supported either
+ COPY agg_csv FROM STDIN;
+ ERROR: cannot insert into foreign table "agg_csv"
-- constraint exclusion tests
\t on
EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
***************
*** 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;
--- 318,324 ----
(0 rows)
COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
! ERROR: cannot insert into foreign table "p1"
CONTEXT: COPY pt, line 2: "1,qux"
COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
SELECT tableoid::regclass, * FROM pt;
***************
*** 342,351 **** SELECT tableoid::regclass, * FROM p2;
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot route inserted tuples to a foreign table
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
--- 345,354 ----
(2 rows)
INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR: cannot insert into foreign table "p1"
INSERT INTO pt VALUES (2, 'xyzzy');
UPDATE pt set a = 1 where a = 2; -- ERROR
! ERROR: cannot insert into foreign table "p1"
SELECT tableoid::regclass, * FROM pt;
tableoid | a | b
----------+---+-------
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7371,7376 **** NOTICE: drop cascades to foreign table bar2
--- 7371,7710 ----
drop table loct1;
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ -- Test insert tuple routing
+ create table itrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+ insert into itrtest values (1, 'foo');
+ insert into itrtest values (1, 'bar') returning *;
+ a | b
+ ---+-----
+ 1 | bar
+ (1 row)
+
+ insert into itrtest values (2, 'baz');
+ insert into itrtest values (2, 'qux') returning *;
+ a | b
+ ---+-----
+ 2 | qux
+ (1 row)
+
+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
+ a | b
+ ---+-------
+ 1 | test1
+ 2 | test2
+ (2 rows)
+
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b
+ ----------+---+-------
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ remp1 | 1 | test1
+ remp2 | 2 | baz
+ remp2 | 2 | qux
+ remp2 | 2 | test2
+ (6 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-------
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ remp1 | 1 | test1
+ (3 rows)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | a
+ ----------+-------+---
+ remp2 | baz | 2
+ remp2 | qux | 2
+ remp2 | test2 | 2
+ (3 rows)
+
+ delete from itrtest;
+ create unique index loct1_idx on loct1 (a);
+ -- DO NOTHING without an inference specification is supported
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ a | b
+ ---+-----
+ 1 | foo
+ (1 row)
+
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ a | b
+ ---+---
+ (0 rows)
+
+ -- But other cases are not supported
+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ select tableoid::regclass, * FROM itrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ (1 row)
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+ -- Test update tuple routing
+ create table utrtest (a int, b text) partition by list (a);
+ create table loct (a int check (a in (1)), b text);
+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+ insert into utrtest values (1, 'foo');
+ insert into utrtest values (2, 'qux');
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ locp | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+-----
+ locp | 2 | qux
+ (1 row)
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where b = 'foo' returning *;
+ ERROR: new row for relation "loct" violates check constraint "loct_a_check"
+ DETAIL: Failing row contains (2, foo).
+ CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
+ -- But the reverse is allowed
+ update utrtest set a = 1 where b = 'qux' returning *;
+ a | b
+ ---+-----
+ 1 | qux
+ (1 row)
+
+ select tableoid::regclass, * FROM utrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ remp | 1 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp;
+ tableoid | a | b
+ ----------+---+-----
+ remp | 1 | foo
+ remp | 1 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM locp;
+ tableoid | a | b
+ ----------+---+---
+ (0 rows)
+
+ -- The executor should not let unexercised FDWs shut down
+ update utrtest set a = 1 where b = 'foo';
+ drop table utrtest;
+ drop table loct;
+ -- Test copy tuple routing
+ create table ctrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+ copy ctrtest from stdin;
+ select tableoid::regclass, * FROM ctrtest;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ remp2 | 2 | qux
+ (2 rows)
+
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ (1 row)
+
+ select tableoid::regclass, * FROM remp2;
+ tableoid | b | a
+ ----------+-----+---
+ remp2 | qux | 2
+ (1 row)
+
+ -- Copying into foreign partitions directly should work as well
+ copy remp1 from stdin;
+ select tableoid::regclass, * FROM remp1;
+ tableoid | a | b
+ ----------+---+-----
+ remp1 | 1 | foo
+ remp1 | 1 | bar
+ (2 rows)
+
+ drop table ctrtest;
+ drop table loct1;
+ drop table loct2;
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+ -- Test basic functionality
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ delete from rem2;
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+ -- check constraint is enforced on the remote side, not locally
+ copy rem2 from stdin;
+ copy rem2 from stdin; -- ERROR
+ ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive"
+ DETAIL: Failing row contains (-1, xyzzy).
+ CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2)
+ COPY rem2, line 1: "-1 xyzzy"
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+ delete from rem2;
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ copy rem2 from stdin;
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
+ select * from rem2;
+ f1 | f2
+ ----+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+ delete from rem2;
+ create trigger trig_row_before_insert before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insert on rem2;
+ delete from rem2;
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on rem2;
+ delete from rem2;
+ -- Test remote triggers
+ create trigger trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger trig_row_before_insert on loc2;
+ delete from rem2;
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+ -- Nothing happens
+ copy rem2 from stdin;
+ select * from rem2;
+ f1 | f2
+ ----+----
+ (0 rows)
+
+ drop trigger trig_null on loc2;
+ delete from rem2;
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+ copy rem2 from stdin;
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (1,foo)
+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
+ NOTICE: NEW: (2,bar)
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (1,"foo triggered !")
+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
+ NOTICE: NEW: (2,"bar triggered !")
+ select * from rem2;
+ f1 | f2
+ ----+-----------------
+ 1 | foo triggered !
+ 2 | bar triggered !
+ (2 rows)
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insert on loc2;
+ delete from rem2;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 319,324 **** static TupleTableSlot *postgresExecForeignDelete(EState *estate,
--- 319,328 ----
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
+ static void postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo);
+ static void postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo);
static int postgresIsForeignRelUpdatable(Relation rel);
static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
***************
*** 473,478 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 477,484 ----
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
+ routine->BeginForeignInsert = postgresBeginForeignInsert;
+ routine->EndForeignInsert = postgresEndForeignInsert;
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
routine->PlanDirectModify = postgresPlanDirectModify;
routine->BeginDirectModify = postgresBeginDirectModify;
***************
*** 1960,1965 **** postgresEndForeignModify(EState *estate,
--- 1966,2061 ----
}
/*
+ * postgresBeginForeignInsert
+ * Begin an insert operation on a foreign table
+ */
+ static void
+ postgresBeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate;
+ Plan *plan = mtstate->ps.plan;
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ RangeTblEntry *rte;
+ Query *query;
+ PlannerInfo *root;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int attnum;
+ StringInfoData sql;
+ List *targetAttrs = NIL;
+ List *retrieved_attrs = NIL;
+ bool doNothing = false;
+
+ initStringInfo(&sql);
+
+ /* Set up largely-dummy planner state. */
+ rte = makeNode(RangeTblEntry);
+ rte->rtekind = RTE_RELATION;
+ rte->relid = RelationGetRelid(rel);
+ rte->relkind = RELKIND_FOREIGN_TABLE;
+ query = makeNode(Query);
+ query->commandType = CMD_INSERT;
+ query->resultRelation = 1;
+ query->rtable = list_make1(rte);
+ root = makeNode(PlannerInfo);
+ root->parse = query;
+
+ /* We transmit all columns that are defined in the foreign table. */
+ for (attnum = 1; attnum <= tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (!attr->attisdropped)
+ targetAttrs = lappend_int(targetAttrs, attnum);
+ }
+
+ /* Check if we add the ON CONFLICT clause to the remote query. */
+ if (plan)
+ {
+ OnConflictAction onConflictAction = ((ModifyTable *) plan)->onConflictAction;
+
+ /* We only support DO NOTHING without an inference specification. */
+ if (onConflictAction == ONCONFLICT_NOTHING)
+ doNothing = true;
+ else if (onConflictAction != ONCONFLICT_NONE)
+ elog(ERROR, "unexpected ON CONFLICT specification: %d",
+ (int) onConflictAction);
+ }
+
+ /* Construct the SQL command string. */
+ deparseInsertSql(&sql, root, 1, rel, targetAttrs, doNothing,
+ resultRelInfo->ri_returningList, &retrieved_attrs);
+
+ /* Construct an execution state. */
+ fmstate = create_foreign_modify(mtstate->ps.state,
+ resultRelInfo,
+ CMD_INSERT,
+ NULL,
+ sql.data,
+ targetAttrs,
+ retrieved_attrs != NIL,
+ retrieved_attrs);
+
+ resultRelInfo->ri_FdwState = fmstate;
+ }
+
+ /*
+ * postgresEndForeignInsert
+ * Finish an insert operation on a foreign table
+ */
+ static void
+ postgresEndForeignInsert(EState *estate,
+ ResultRelInfo *resultRelInfo)
+ {
+ PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+
+ Assert(fmstate != NULL);
+
+ /* Destroy the execution state */
+ finish_foreign_modify(fmstate);
+ }
+
+ /*
* postgresIsForeignRelUpdatable
* Determine whether a foreign table supports INSERT, UPDATE and/or
* DELETE.
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1768,1773 **** drop table loct1;
--- 1768,2010 ----
drop table loct2;
-- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+
+ -- Test insert tuple routing
+ create table itrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table itrtest attach partition remp1 for values in (1);
+ alter table itrtest attach partition remp2 for values in (2);
+
+ insert into itrtest values (1, 'foo');
+ insert into itrtest values (1, 'bar') returning *;
+ insert into itrtest values (2, 'baz');
+ insert into itrtest values (2, 'qux') returning *;
+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
+
+ select tableoid::regclass, * FROM itrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ delete from itrtest;
+
+ create unique index loct1_idx on loct1 (a);
+
+ -- DO NOTHING without an inference specification is supported
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
+
+ -- But other cases are not supported
+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
+
+ select tableoid::regclass, * FROM itrtest;
+
+ drop table itrtest;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test update tuple routing
+ create table utrtest (a int, b text) partition by list (a);
+ create table loct (a int check (a in (1)), b text);
+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
+ create table locp (a int check (a in (2)), b text);
+ alter table utrtest attach partition remp for values in (1);
+ alter table utrtest attach partition locp for values in (2);
+
+ insert into utrtest values (1, 'foo');
+ insert into utrtest values (2, 'qux');
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- It's not allowed to move a row from a partition that is foreign to another
+ update utrtest set a = 2 where b = 'foo' returning *;
+
+ -- But the reverse is allowed
+ update utrtest set a = 1 where b = 'qux' returning *;
+
+ select tableoid::regclass, * FROM utrtest;
+ select tableoid::regclass, * FROM remp;
+ select tableoid::regclass, * FROM locp;
+
+ -- The executor should not let unexercised FDWs shut down
+ update utrtest set a = 1 where b = 'foo';
+
+ drop table utrtest;
+ drop table loct;
+
+ -- Test copy tuple routing
+ create table ctrtest (a int, b text) partition by list (a);
+ create table loct1 (a int check (a in (1)), b text);
+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
+ create table loct2 (a int check (a in (2)), b text);
+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
+ alter table ctrtest attach partition remp1 for values in (1);
+ alter table ctrtest attach partition remp2 for values in (2);
+
+ copy ctrtest from stdin;
+ 1 foo
+ 2 qux
+ \.
+
+ select tableoid::regclass, * FROM ctrtest;
+ select tableoid::regclass, * FROM remp1;
+ select tableoid::regclass, * FROM remp2;
+
+ -- Copying into foreign partitions directly should work as well
+ copy remp1 from stdin;
+ 1 bar
+ \.
+
+ select tableoid::regclass, * FROM remp1;
+
+ drop table ctrtest;
+ drop table loct1;
+ drop table loct2;
+
+ -- ===================================================================
+ -- test COPY FROM
+ -- ===================================================================
+
+ create table loc2 (f1 int, f2 text);
+ alter table loc2 set (autovacuum_enabled = 'false');
+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
+
+ -- Test basic functionality
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ delete from rem2;
+
+ -- Test check constraints
+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
+
+ -- check constraint is enforced on the remote side, not locally
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ copy rem2 from stdin; -- ERROR
+ -1 xyzzy
+ \.
+ select * from rem2;
+
+ alter foreign table rem2 drop constraint rem2_f1positive;
+ alter table loc2 drop constraint loc2_f1positive;
+
+ delete from rem2;
+
+ -- Test local triggers
+ create trigger trig_stmt_before before insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_stmt_after after insert on rem2
+ for each statement execute procedure trigger_func();
+ create trigger trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before on rem2;
+ drop trigger trig_row_after on rem2;
+ drop trigger trig_stmt_before on rem2;
+ drop trigger trig_stmt_after on rem2;
+
+ delete from rem2;
+
+ create trigger trig_row_before_insert before insert on rem2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insert on rem2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on rem2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on rem2;
+
+ delete from rem2;
+
+ -- Test remote triggers
+ create trigger trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ -- The new values are concatenated with ' triggered !'
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_row_before_insert on loc2;
+
+ delete from rem2;
+
+ create trigger trig_null before insert on loc2
+ for each row execute procedure trig_null();
+
+ -- Nothing happens
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger trig_null on loc2;
+
+ delete from rem2;
+
+ -- Test a combination of local and remote triggers
+ create trigger rem2_trig_row_before before insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger rem2_trig_row_after after insert on rem2
+ for each row execute procedure trigger_data(23,'skidoo');
+ create trigger loc2_trig_row_before_insert before insert on loc2
+ for each row execute procedure trig_row_before_insupdate();
+
+ copy rem2 from stdin;
+ 1 foo
+ 2 bar
+ \.
+ select * from rem2;
+
+ drop trigger rem2_trig_row_before on rem2;
+ drop trigger rem2_trig_row_after on rem2;
+ drop trigger loc2_trig_row_before_insert on loc2;
+
+ delete from rem2;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 3037,3047 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! Partitions can also be foreign tables
! (see <xref linkend="sql-createforeigntable"/>),
! although these have some limitations that normal tables do not. For
! example, data inserted into the partitioned table is not routed to
! foreign table partitions.
</para>
<para>
--- 3037,3045 ----
</para>
<para>
! Partitions can also be foreign tables, although they have some limitations
! that normal tables do not; see <xref linkend="sql-createforeigntable"> for
! more information.
</para>
<para>
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 695,700 **** EndForeignModify(EState *estate,
--- 695,766 ----
</para>
<para>
+ Tuples inserted into a partitioned table by <command>INSERT</command> or
+ <command>COPY FROM</command> are routed to partitions. If an FDW
+ supports routable foreign-table partitions, it should also provide the
+ following callback functions. These functions are also called when
+ <command>COPY FROM</command> is executed on a foreign table.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ BeginForeignInsert(ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ Begin executing an insert operation on a foreign table. This routine is
+ called right before the first tuple is inserted into the foreign table
+ in both cases when it is the partition chosen for tuple routing and the
+ target specified in a <command>COPY FROM</command> command. It should
+ perform any initialization needed prior to the actual insertion.
+ Subsequently, <function>ExecForeignInsert</function> will be called for
+ each tuple to be inserted into the foreign table.
+ </para>
+
+ <para>
+ <literal>mtstate</literal> is the overall state of the
+ <structname>ModifyTable</structname> plan node being executed; global data about
+ the plan and execution state is available via this structure.
+ <literal>rinfo</literal> is the <structname>ResultRelInfo</structname> struct describing
+ the target foreign table. (The <structfield>ri_FdwState</structfield> field of
+ <structname>ResultRelInfo</structname> is available for the FDW to store any
+ private state it needs for this operation.)
+ </para>
+
+ <para>
+ When this is called by a <command>COPY FROM</command> command, the
+ plan-related global data in <literal>mtstate</literal> is not provided
+ and the <literal>planSlot</literal> parameter of
+ <function>ExecForeignInsert</function> subsequently called for each
+ inserted tuple is <literal>NULL</literal>, whether the foreign table is
+ the partition chosen for tuple routing or the target specified in the
+ command.
+ </para>
+
+ <para>
+ If the <function>BeginForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the initialization.
+ </para>
+
+ <para>
+ <programlisting>
+ void
+ EndForeignInsert(EState *estate,
+ ResultRelInfo *rinfo);
+ </programlisting>
+
+ End the insert operation and release resources. It is normally not important
+ to release palloc'd memory, but for example open files and connections
+ to remote servers should be cleaned up.
+ </para>
+
+ <para>
+ If the <function>EndForeignInsert</function> pointer is set to
+ <literal>NULL</literal>, no action is taken for the termination.
+ </para>
+
+ <para>
<programlisting>
int
IsForeignRelUpdatable(Relation rel);
*** a/doc/src/sgml/ref/copy.sgml
--- b/doc/src/sgml/ref/copy.sgml
***************
*** 402,409 **** COPY <replaceable class="parameter">count</replaceable>
</para>
<para>
! <command>COPY FROM</command> can be used with plain tables and with views
! that have <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
--- 402,410 ----
</para>
<para>
! <command>COPY FROM</command> can be used with plain, foreign, or
! partitioned tables or with views that have
! <literal>INSTEAD OF INSERT</literal> triggers.
</para>
<para>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 291,296 **** UPDATE <replaceable class="parameter">count</replaceable>
--- 291,299 ----
concurrent <command>UPDATE</command> or <command>DELETE</command> on the
same row may miss this row. For details see the section
<xref linkend="ddl-partitioning-declarative-limitations"/>.
+ Currently, it is not allowed to move a row from a partition that is a
+ foreign table to another, but the reverse is allowed if the foreign table
+ is routable.
</para>
</refsect1>
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 29,34 ****
--- 29,35 ----
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
***************
*** 2284,2289 **** CopyFrom(CopyState cstate)
--- 2285,2291 ----
ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
+ ModifyTableState *mtstate;
ExprContext *econtext;
TupleTableSlot *myslot;
MemoryContext oldcontext = CurrentMemoryContext;
***************
*** 2305,2315 **** CopyFrom(CopyState cstate)
Assert(cstate->rel);
/*
! * The target must be a plain relation or have an INSTEAD OF INSERT row
! * trigger. (Currently, such triggers are only allowed on views, so we
! * only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
--- 2307,2318 ----
Assert(cstate->rel);
/*
! * The target must be a plain, foreign, or partitioned relation, or have
! * an INSTEAD OF INSERT row trigger. (Currently, such triggers are only
! * allowed on views, so we only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
***************
*** 2325,2335 **** CopyFrom(CopyState cstate)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy to materialized view \"%s\"",
RelationGetRelationName(cstate->rel))));
- else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy to foreign table \"%s\"",
- RelationGetRelationName(cstate->rel))));
else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
--- 2328,2333 ----
***************
*** 2436,2441 **** CopyFrom(CopyState cstate)
--- 2434,2442 ----
NULL,
0);
+ /* Verify the named relation is a valid target for INSERT */
+ CheckValidResultRel(resultRelInfo, CMD_INSERT);
+
ExecOpenIndices(resultRelInfo, false);
estate->es_result_relations = resultRelInfo;
***************
*** 2448,2453 **** CopyFrom(CopyState cstate)
--- 2449,2469 ----
/* Triggers might need a slot as well */
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+ /*
+ * Set up a ModifyTableState so we can let FDW(s) init themselves for
+ * foreign-table result relation(s).
+ */
+ mtstate = makeNode(ModifyTableState);
+ mtstate->ps.plan = NULL;
+ mtstate->ps.state = estate;
+ mtstate->operation = CMD_INSERT;
+ mtstate->resultRelInfo = estate->es_result_relations;
+
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate,
+ resultRelInfo);
+
/* Prepare to catch AFTER triggers. */
AfterTriggerBeginQuery();
***************
*** 2489,2499 **** CopyFrom(CopyState cstate)
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
--- 2505,2516 ----
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there. We also can't do
! * it if the table is foreign or partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ resultRelInfo->ri_FdwRoutine != NULL ||
cstate->partition_tuple_routing != NULL ||
cstate->volatile_defexprs)
{
***************
*** 2608,2626 **** CopyFrom(CopyState cstate)
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
! resultRelInfo = ExecInitPartitionInfo(NULL,
saved_resultRelInfo,
proute, estate,
leaf_part_index);
Assert(resultRelInfo != NULL);
}
- /* 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
*/
--- 2625,2637 ----
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
! resultRelInfo = ExecInitPartitionInfo(mtstate,
saved_resultRelInfo,
proute, estate,
leaf_part_index);
Assert(resultRelInfo != NULL);
}
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
*/
***************
*** 2708,2716 **** CopyFrom(CopyState cstate)
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /* Check the constraints of the tuple */
! if (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
--- 2719,2731 ----
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
! /*
! * If the target is a plain table, check the constraints of
! * the tuple.
! */
! if (resultRelInfo->ri_FdwRoutine == NULL &&
! (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr))
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
***************
*** 2742,2751 **** CopyFrom(CopyState cstate)
{
List *recheckIndexes = NIL;
! /* OK, store the tuple and create index entries for it */
! heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
! hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
--- 2757,2788 ----
{
List *recheckIndexes = NIL;
! /* OK, store the tuple */
! if (resultRelInfo->ri_FdwRoutine != NULL)
! {
! slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
! resultRelInfo,
! slot,
! NULL);
!
! if (slot == NULL) /* "do nothing" */
! goto next_tuple;
!
! /* FDW might have changed tuple */
! tuple = ExecMaterializeSlot(slot);
+ /*
+ * AFTER ROW Triggers might reference the tableoid
+ * column, so initialize t_tableOid before evaluating
+ * them.
+ */
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+ else
+ heap_insert(resultRelInfo->ri_RelationDesc, tuple,
+ mycid, hi_options, bistate);
+
+ /* And create index entries for it */
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
&(tuple->t_self),
***************
*** 2763,2775 **** CopyFrom(CopyState cstate)
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger;
! * this is the same definition used by execMain.c for counting
! * tuples inserted by an INSERT command.
*/
processed++;
}
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
--- 2800,2813 ----
}
/*
! * We count only tuples not suppressed by a BEFORE INSERT trigger
! * or FDW; this is the same definition used by nodeModifyTable.c
! * for counting tuples inserted by an INSERT command.
*/
processed++;
}
+ next_tuple:
/* Restore the saved ResultRelInfo */
if (saved_resultRelInfo)
{
***************
*** 2810,2820 **** CopyFrom(CopyState cstate)
ExecResetTupleTable(estate->es_tupleTable, false);
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
--- 2848,2864 ----
ExecResetTupleTable(estate->es_tupleTable, false);
+ /* Allow the FDW to shut down */
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(estate,
+ resultRelInfo);
+
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_tuple_routing)
! ExecCleanupTupleRouting(mtstate, cstate->partition_tuple_routing);
/* Close any trigger target relations */
ExecCleanUpTriggerState(estate);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1179,1191 **** 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),
--- 1179,1184 ----
***************
*** 1378,1383 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1371,1377 ----
resultRelInfo->ri_PartitionCheck = partition_check;
resultRelInfo->ri_PartitionRoot = partition_root;
+ resultRelInfo->ri_PartitionReadyForRouting = false;
}
/*
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 18,23 ****
--- 18,24 ----
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+ #include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
***************
*** 55,66 **** static List *adjust_partition_tlist(List *tlist, TupleConversionMap *map);
* see ExecInitPartitionInfo. However, if the function is invoked for update
* tuple routing, caller would already have initialized ResultRelInfo's for
* some of the partitions, which are reused and assigned to their respective
! * slot in the aforementioned array.
*/
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
- TupleDesc tupDesc = RelationGetDescr(rel);
List *leaf_parts;
ListCell *cell;
int i;
--- 56,68 ----
* see ExecInitPartitionInfo. However, if the function is invoked for update
* tuple routing, caller would already have initialized ResultRelInfo's for
* some of the partitions, which are reused and assigned to their respective
! * slot in the aforementioned array. For such partitions, we delay setting
! * up objects such as TupleConversionMap until those are actually chosen as
! * the partitions to route tuples to. See ExecPrepareTupleRouting.
*/
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
{
List *leaf_parts;
ListCell *cell;
int i;
***************
*** 141,151 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
if (update_rri_index < num_update_rri &&
RelationGetRelid(update_rri[update_rri_index].ri_RelationDesc) == leaf_oid)
{
- Relation partrel;
- TupleDesc part_tupdesc;
-
leaf_part_rri = &update_rri[update_rri_index];
- partrel = leaf_part_rri->ri_RelationDesc;
/*
* This is required in order to convert the partition's tuple to
--- 143,149 ----
***************
*** 159,181 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
proute->subplan_partition_offsets[update_rri_index] = i;
update_rri_index++;
-
- 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.
- */
- proute->parent_child_tupconv_maps[i] =
- convert_tuples_by_name(tupDesc, part_tupdesc,
- gettext_noop("could not convert row type"));
-
- /*
- * Verify result relation is a valid target for an INSERT. An
- * UPDATE of a partition-key becomes a DELETE+INSERT operation, so
- * this check is required even when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
}
proute->partitions[i] = leaf_part_rri;
--- 157,162 ----
***************
*** 342,351 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
PartitionTupleRouting *proute,
EState *estate, int partidx)
{
Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel;
ResultRelInfo *leaf_part_rri;
- ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
MemoryContext oldContext;
/*
--- 323,332 ----
PartitionTupleRouting *proute,
EState *estate, int partidx)
{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel;
ResultRelInfo *leaf_part_rri;
MemoryContext oldContext;
/*
***************
*** 370,382 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
leaf_part_rri->ri_PartitionLeafIndex = partidx;
/*
- * Verify result relation is a valid target for an INSERT. An UPDATE of a
- * partition-key becomes a DELETE+INSERT operation, so this check is still
- * required when the operation is CMD_UPDATE.
- */
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
-
- /*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
*
--- 351,356 ----
***************
*** 388,393 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 362,370 ----
lappend(estate->es_tuple_routing_result_relations,
leaf_part_rri);
+ /* Set up information needed for routing tuples to this partition. */
+ ExecInitRoutingInfo(mtstate, estate, proute, leaf_part_rri, partidx);
+
/*
* Open partition indices. The user may have asked to check for conflicts
* within this leaf partition and do "nothing" instead of throwing an
***************
*** 493,498 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 470,476 ----
returningList = map_partition_varattnos(returningList, firstVarno,
partrel, firstResultRel,
NULL);
+ leaf_part_rri->ri_returningList = returningList;
/*
* Initialize the projection itself.
***************
*** 510,524 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
}
/*
- * Save a tuple conversion map to convert a tuple routed to this partition
- * from the parent's type to the partition's.
- */
- proute->parent_child_tupconv_maps[partidx] =
- convert_tuples_by_name(RelationGetDescr(rootrel),
- RelationGetDescr(partrel),
- gettext_noop("could not convert row type"));
-
- /*
* If there is an ON CONFLICT clause, initialize state for it.
*/
if (node && node->onConflictAction != ONCONFLICT_NONE)
--- 488,493 ----
***************
*** 747,752 **** ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 716,765 ----
}
/*
+ * ExecInitRoutingInfo
+ * Set up information needed for routing tuples to a leaf partition if
+ * routable; else abort the operation
+ */
+ void
+ ExecInitRoutingInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx)
+ {
+ MemoryContext oldContext;
+
+ /* Verify the partition is a valid target for INSERT */
+ CheckValidResultRel(partRelInfo, CMD_INSERT);
+
+ /*
+ * Switch into per-query memory context.
+ */
+ oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * Set up a tuple conversion map to convert a tuple routed to the
+ * partition from the parent's type to the partition's.
+ */
+ proute->parent_child_tupconv_maps[partidx] =
+ convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
+ RelationGetDescr(partRelInfo->ri_RelationDesc),
+ gettext_noop("could not convert row type"));
+
+ /*
+ * If the partition is a foreign table, let the FDW init itself for
+ * routing tuples to the partition.
+ */
+ if (partRelInfo->ri_FdwRoutine != NULL &&
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
+ partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
+
+ MemoryContextSwitchTo(oldContext);
+
+ partRelInfo->ri_PartitionReadyForRouting = true;
+ }
+
+ /*
* ExecSetupChildParentMapForLeaf -- Initialize the per-leaf-partition
* child-to-root tuple conversion map array.
*
***************
*** 848,854 **** ConvertPartitionTupleSlot(TupleConversionMap *map,
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
--- 861,868 ----
* Close all the partitioned tables, leaf partitions, and their indices.
*/
void
! ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute)
{
int i;
int subplan_index = 0;
***************
*** 876,881 **** ExecCleanupTupleRouting(PartitionTupleRouting *proute)
--- 890,902 ----
if (resultRelInfo == NULL)
continue;
+ /* Allow any FDWs to shut down if they've been exercised */
+ if (resultRelInfo->ri_PartitionReadyForRouting &&
+ resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignInsert(mtstate->ps.state,
+ resultRelInfo);
+
/*
* If this result rel is one of the UPDATE subplan result rels, let
* ExecEndPlan() close it. For INSERT or COPY,
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 1831,1841 **** ExecPrepareTupleRouting(ModifyTableState *mtstate,
proute, estate,
partidx);
! /* We do not yet have a way to insert into a foreign partition */
! if (partrel->ri_FdwRoutine)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot route inserted tuples to a foreign table")));
/*
* Make it look like we are inserting into the partition.
--- 1831,1851 ----
proute, estate,
partidx);
! /*
! * Set up information needed for routing tuples to the partition if we
! * didn't yet (ExecInitRoutingInfo would abort the operation if the
! * partition isn't routable).
! *
! * Note: an UPDATE of a partition key invokes an INSERT that moves the
! * tuple to a new partition. This setup would be needed for a subplan
! * partition of such an UPDATE that is chosen as the partition to route
! * the tuple to. The reason we do this setup here rather than in
! * ExecSetupPartitionTupleRouting is to avoid aborting such an UPDATE
! * unnecessarily due to non-routable subplan partitions that may not be
! * chosen for update tuple movement after all.
! */
! if (!partrel->ri_PartitionReadyForRouting)
! ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
/*
* Make it look like we are inserting into the partition.
***************
*** 2536,2541 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 2546,2552 ----
{
List *rlist = (List *) lfirst(l);
+ resultRelInfo->ri_returningList = rlist;
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
***************
*** 2931,2937 **** ExecEndModifyTable(ModifyTableState *node)
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node->mt_partition_tuple_routing);
/*
* Free the exprcontext
--- 2942,2948 ----
/* Close all the partitioned tables, leaf partitions, and their indices */
if (node->mt_partition_tuple_routing)
! ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
/*
* Free the exprcontext
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 119,124 **** extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
--- 119,129 ----
ResultRelInfo *resultRelInfo,
PartitionTupleRouting *proute,
EState *estate, int partidx);
+ extern void ExecInitRoutingInfo(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *partRelInfo,
+ int partidx);
extern void ExecSetupChildParentMapForLeaf(PartitionTupleRouting *proute);
extern TupleConversionMap *TupConvMapForLeaf(PartitionTupleRouting *proute,
ResultRelInfo *rootRelInfo, int leaf_index);
***************
*** 126,131 **** extern HeapTuple ConvertPartitionTupleSlot(TupleConversionMap *map,
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
--- 131,137 ----
HeapTuple tuple,
TupleTableSlot *new_slot,
TupleTableSlot **p_my_slot);
! extern void ExecCleanupTupleRouting(ModifyTableState *mtstate,
! PartitionTupleRouting *proute);
#endif /* EXECPARTITION_H */
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 98,103 **** typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
--- 98,109 ----
typedef void (*EndForeignModify_function) (EState *estate,
ResultRelInfo *rinfo);
+ typedef void (*BeginForeignInsert_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo);
+
+ typedef void (*EndForeignInsert_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
typedef int (*IsForeignRelUpdatable_function) (Relation rel);
typedef bool (*PlanDirectModify_function) (PlannerInfo *root,
***************
*** 205,210 **** typedef struct FdwRoutine
--- 211,218 ----
ExecForeignUpdate_function ExecForeignUpdate;
ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify;
+ BeginForeignInsert_function BeginForeignInsert;
+ EndForeignInsert_function EndForeignInsert;
IsForeignRelUpdatable_function IsForeignRelUpdatable;
PlanDirectModify_function PlanDirectModify;
BeginDirectModify_function BeginDirectModify;
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 444,449 **** typedef struct ResultRelInfo
--- 444,452 ----
/* for removing junk attributes from tuples */
JunkFilter *ri_junkFilter;
+ /* list of RETURNING expressions */
+ List *ri_returningList;
+
/* for computing a RETURNING list */
ProjectionInfo *ri_projectReturning;
***************
*** 462,467 **** typedef struct ResultRelInfo
--- 465,473 ----
/* relation descriptor for root partitioned table */
Relation ri_PartitionRoot;
+ /* true if ready for tuple routing */
+ bool ri_PartitionReadyForRouting;
+
int ri_PartitionLeafIndex;
/* for running MERGE on this result relation */
MergeState *ri_mergeState;
0002_copy-from-check-constraint-fix.patchtext/x-diff; name=0002_copy-from-check-constraint-fix.patchDownload
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2709,2715 **** CopyFrom(CopyState cstate)
check_partition_constr = false;
/* Check the constraints of the tuple */
! if (cstate->rel->rd_att->constr || check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
--- 2709,2716 ----
check_partition_constr = false;
/* Check the constraints of the tuple */
! if (resultRelInfo->ri_RelationDesc->rd_att->constr ||
! check_partition_constr)
ExecConstraints(resultRelInfo, slot, estate, true);
if (useHeapMultiInsert)
On Thu, Apr 5, 2018 at 6:21 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
Attached is an updated version of the patch set plus the patch in [1]. Patch
0003_foreign-routing-fdwapi-6.patch can be applied on top of patch
0001_postgres-fdw-refactoring-6.patch and
0002_copy-from-check-constraint-fix.patch.
Committed. Let me say that you do nice work.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 2018-04-06 19:25:20 -0400, Robert Haas wrote:
On Thu, Apr 5, 2018 at 6:21 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:Attached is an updated version of the patch set plus the patch in [1]. Patch
0003_foreign-routing-fdwapi-6.patch can be applied on top of patch
0001_postgres-fdw-refactoring-6.patch and
0002_copy-from-check-constraint-fix.patch.Committed. Let me say that you do nice work.
The CF entry for this is still marked as 'ready for committer'. Does anything remain?
https://commitfest.postgresql.org/17/1184/
Greetings,
Andres Freund
On Sun, Apr 8, 2018 at 5:37 AM, Andres Freund <andres@anarazel.de> wrote:
On 2018-04-06 19:25:20 -0400, Robert Haas wrote:
On Thu, Apr 5, 2018 at 6:21 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:Attached is an updated version of the patch set plus the patch in [1]. Patch
0003_foreign-routing-fdwapi-6.patch can be applied on top of patch
0001_postgres-fdw-refactoring-6.patch and
0002_copy-from-check-constraint-fix.patch.Committed. Let me say that you do nice work.
The CF entry for this is still marked as 'ready for committer'. Does anything remain?
Nothing remains, so marked as committed.
Thanks,
Amit
(2018/04/07 8:25), Robert Haas wrote:
On Thu, Apr 5, 2018 at 6:21 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:Attached is an updated version of the patch set plus the patch in [1]. Patch
0003_foreign-routing-fdwapi-6.patch can be applied on top of patch
0001_postgres-fdw-refactoring-6.patch and
0002_copy-from-check-constraint-fix.patch.Committed. Let me say that you do nice work.
Thanks to Robert for committing! Thanks to everyone who got involved in
this patch for all of the help with review and commentary!
Best regards,
Etsuro Fujita