summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/copy.c15
-rw-r--r--src/backend/commands/explain.c35
-rw-r--r--src/backend/executor/execMain.c42
-rw-r--r--src/backend/executor/nodeForeignscan.c3
-rw-r--r--src/backend/executor/nodeModifyTable.c151
-rw-r--r--src/backend/nodes/copyfuncs.c1
-rw-r--r--src/backend/nodes/outfuncs.c1
-rw-r--r--src/backend/optimizer/plan/createplan.c54
-rw-r--r--src/backend/optimizer/plan/planner.c15
-rw-r--r--src/backend/optimizer/prep/preptlist.c3
-rw-r--r--src/backend/parser/analyze.c13
-rw-r--r--src/backend/rewrite/rewriteHandler.c27
-rw-r--r--src/include/foreign/fdwapi.h69
-rw-r--r--src/include/nodes/execnodes.h7
-rw-r--r--src/include/nodes/plannodes.h38
-rw-r--r--src/include/optimizer/planmain.h3
16 files changed, 399 insertions, 78 deletions
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 4825bca363f..4eb94a43add 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2121,17 +2121,10 @@ CopyFrom(CopyState cstate)
* here that basically duplicated execUtils.c ...)
*/
resultRelInfo = makeNode(ResultRelInfo);
- resultRelInfo->ri_RangeTableIndex = 1; /* dummy */
- resultRelInfo->ri_RelationDesc = cstate->rel;
- resultRelInfo->ri_TrigDesc = CopyTriggerDesc(cstate->rel->trigdesc);
- if (resultRelInfo->ri_TrigDesc)
- {
- resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
- palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
- resultRelInfo->ri_TrigWhenExprs = (List **)
- palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(List *));
- }
- resultRelInfo->ri_TrigInstrument = NULL;
+ InitResultRelInfo(resultRelInfo,
+ cstate->rel,
+ 1, /* dummy rangetable index */
+ 0);
ExecOpenIndices(resultRelInfo);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 989b52da9d4..9799e9ecb41 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -90,6 +90,7 @@ static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
+static void show_modifytable_info(ModifyTableState *mtstate, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
@@ -1341,6 +1342,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
break;
+ case T_ModifyTable:
+ show_modifytable_info((ModifyTableState *) planstate, es);
+ break;
case T_Hash:
show_hash_info((HashState *) planstate, es);
break;
@@ -1840,7 +1844,8 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
FdwRoutine *fdwroutine = fsstate->fdwroutine;
/* Let the FDW emit whatever fields it wants */
- fdwroutine->ExplainForeignScan(fsstate, es);
+ if (fdwroutine->ExplainForeignScan != NULL)
+ fdwroutine->ExplainForeignScan(fsstate, es);
}
/*
@@ -2037,6 +2042,34 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
}
/*
+ * Show extra information for a ModifyTable node
+ */
+static void
+show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
+{
+ FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
+
+ /*
+ * If the first target relation is a foreign table, call its FDW to
+ * display whatever additional fields it wants to. For now, we ignore the
+ * possibility of other targets being foreign tables, although the API for
+ * ExplainForeignModify is designed to allow them to be processed.
+ */
+ if (fdwroutine != NULL &&
+ fdwroutine->ExplainForeignModify != NULL)
+ {
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ List *fdw_private = (List *) linitial(node->fdwPrivLists);
+
+ fdwroutine->ExplainForeignModify(mtstate,
+ mtstate->resultRelInfo,
+ fdw_private,
+ 0,
+ es);
+ }
+}
+
+/*
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
* BitmapAnd, or BitmapOr node.
*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 288b29e44a9..1f2a23bcdd6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -44,6 +44,7 @@
#include "catalog/namespace.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
+#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
@@ -1005,6 +1006,7 @@ void
CheckValidResultRel(Relation resultRel, CmdType operation)
{
TriggerDesc *trigDesc = resultRel->trigdesc;
+ FdwRoutine *fdwroutine;
switch (resultRel->rd_rel->relkind)
{
@@ -1069,10 +1071,35 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
RelationGetRelationName(resultRel))));
break;
case RELKIND_FOREIGN_TABLE:
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot change foreign table \"%s\"",
- RelationGetRelationName(resultRel))));
+ /* Okay only if the FDW supports it */
+ fdwroutine = GetFdwRoutineForRelation(resultRel, false);
+ switch (operation)
+ {
+ case CMD_INSERT:
+ if (fdwroutine->ExecForeignInsert == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot insert into foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ case CMD_UPDATE:
+ if (fdwroutine->ExecForeignUpdate == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot update foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ case CMD_DELETE:
+ if (fdwroutine->ExecForeignDelete == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot delete from foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ default:
+ elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+ break;
+ }
break;
default:
ereport(ERROR,
@@ -1126,7 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
RelationGetRelationName(rel))));
break;
case RELKIND_FOREIGN_TABLE:
- /* Perhaps we can support this someday, but not today */
+ /* Should not get here */
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot lock rows in foreign table \"%s\"",
@@ -1180,6 +1207,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_TrigWhenExprs = NULL;
resultRelInfo->ri_TrigInstrument = NULL;
}
+ if (resultRelationDesc->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ resultRelInfo->ri_FdwRoutine = GetFdwRoutineForRelation(resultRelationDesc, true);
+ else
+ resultRelInfo->ri_FdwRoutine = NULL;
+ resultRelInfo->ri_FdwState = NULL;
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 63478cd12ad..448fd6a912f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -147,7 +147,8 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->ss.ss_currentRelation = currentRelation;
/*
- * get the scan type from the relation descriptor.
+ * get the scan type from the relation descriptor. (XXX at some point we
+ * might want to let the FDW editorialize on the scan tupdesc.)
*/
ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cb084d03d47..a6f247e1bc3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
+#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
@@ -225,6 +226,24 @@ ExecInsert(TupleTableSlot *slot,
newId = InvalidOid;
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * 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;
+
+ /* FDW might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+
+ newId = InvalidOid;
+ }
else
{
/*
@@ -279,7 +298,9 @@ ExecInsert(TupleTableSlot *slot,
* When deleting from a table, tupleid identifies the tuple to
* delete and oldtuple is NULL. When deleting from a view,
* oldtuple is passed to the INSTEAD OF triggers and identifies
- * what to delete, and tupleid is invalid.
+ * what to delete, and tupleid is invalid. When deleting from a
+ * foreign table, both tupleid and oldtuple are NULL; the FDW has
+ * to figure out which row to delete using data from the planSlot.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
@@ -296,6 +317,7 @@ ExecDelete(ItemPointer tupleid,
Relation resultRelationDesc;
HTSU_Result result;
HeapUpdateFailureData hufd;
+ TupleTableSlot *slot = NULL;
/*
* get information on the (current) result relation
@@ -334,6 +356,27 @@ ExecDelete(ItemPointer tupleid,
if (!dodelete) /* "do nothing" */
return NULL;
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * delete from foreign table: let the FDW do it
+ *
+ * We offer the trigger tuple slot as a place to store RETURNING data,
+ * although the FDW can return some other slot if it wants. Set up
+ * the slot's tupdesc so the FDW doesn't need to do that for itself.
+ */
+ slot = estate->es_trig_tuple_slot;
+ if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+ ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+
+ slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
+ resultRelInfo,
+ slot,
+ planSlot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+ }
else
{
/*
@@ -443,34 +486,49 @@ ldelete:;
* We have to put the target tuple into a slot, which means first we
* gotta fetch it. We can use the trigger tuple slot.
*/
- TupleTableSlot *slot = estate->es_trig_tuple_slot;
TupleTableSlot *rslot;
HeapTupleData deltuple;
Buffer delbuffer;
- if (oldtuple != NULL)
+ if (resultRelInfo->ri_FdwRoutine)
{
- deltuple.t_data = oldtuple;
- deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
- ItemPointerSetInvalid(&(deltuple.t_self));
- deltuple.t_tableOid = InvalidOid;
+ /* FDW must have provided a slot containing the deleted row */
+ Assert(!TupIsNull(slot));
delbuffer = InvalidBuffer;
}
else
{
- deltuple.t_self = *tupleid;
- if (!heap_fetch(resultRelationDesc, SnapshotAny,
- &deltuple, &delbuffer, false, NULL))
- elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
- }
+ slot = estate->es_trig_tuple_slot;
+ if (oldtuple != NULL)
+ {
+ deltuple.t_data = oldtuple;
+ deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
+ ItemPointerSetInvalid(&(deltuple.t_self));
+ deltuple.t_tableOid = InvalidOid;
+ delbuffer = InvalidBuffer;
+ }
+ else
+ {
+ deltuple.t_self = *tupleid;
+ if (!heap_fetch(resultRelationDesc, SnapshotAny,
+ &deltuple, &delbuffer, false, NULL))
+ elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+ }
- if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
- ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
- ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+ if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+ ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+ ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+ }
rslot = ExecProcessReturning(resultRelInfo->ri_projectReturning,
slot, planSlot);
+ /*
+ * Before releasing the target tuple again, make sure rslot has a
+ * local copy of any pass-by-reference values.
+ */
+ ExecMaterializeSlot(rslot);
+
ExecClearTuple(slot);
if (BufferIsValid(delbuffer))
ReleaseBuffer(delbuffer);
@@ -494,7 +552,9 @@ ldelete:;
* When updating a table, tupleid identifies the tuple to
* update and oldtuple is NULL. When updating a view, oldtuple
* is passed to the INSTEAD OF triggers and identifies what to
- * update, and tupleid is invalid.
+ * update, and tupleid is invalid. When updating a foreign table,
+ * both tupleid and oldtuple are NULL; the FDW has to figure out
+ * which row to update using data from the planSlot.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
@@ -568,6 +628,22 @@ ExecUpdate(ItemPointer tupleid,
/* trigger might have changed tuple */
tuple = ExecMaterializeSlot(slot);
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * update in foreign table: let the FDW do it
+ */
+ slot = resultRelInfo->ri_FdwRoutine->ExecForeignUpdate(estate,
+ resultRelInfo,
+ slot,
+ planSlot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+
+ /* FDW might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+ }
else
{
LockTupleMode lockmode;
@@ -867,10 +943,12 @@ ExecModifyTable(ModifyTableState *node)
*/
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
+ char relkind;
Datum datum;
bool isNull;
- if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION)
{
datum = ExecGetJunkAttribute(slot,
junkfilter->jf_junkAttNo,
@@ -884,6 +962,10 @@ ExecModifyTable(ModifyTableState *node)
* ctid!! */
tupleid = &tuple_ctid;
}
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* do nothing; FDW must fetch any junk attrs it wants */
+ }
else
{
datum = ExecGetJunkAttribute(slot,
@@ -1026,6 +1108,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = resultRelInfo;
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
+ /* Also 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->fdwPrivLists, i);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ i,
+ eflags);
+ }
+
resultRelInfo++;
i++;
}
@@ -1180,12 +1275,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
/* For UPDATE/DELETE, find the appropriate junk attr now */
- if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk ctid column");
}
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* FDW must fetch any junk attrs it wants */
+ }
else
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
@@ -1244,6 +1346,19 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
+ * Allow any FDWs to shut down
+ */
+ for (i = 0; i < node->mt_nplans; i++)
+ {
+ ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
+
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+ resultRelInfo);
+ }
+
+ /*
* Free the exprcontext
*/
ExecFreeExprContext(&node->ps);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 867b0c09d90..fd3823a36ee 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -179,6 +179,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_SCALAR_FIELD(resultRelIndex);
COPY_NODE_FIELD(plans);
COPY_NODE_FIELD(returningLists);
+ COPY_NODE_FIELD(fdwPrivLists);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index be4e5482816..d8ce5753a4c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -333,6 +333,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_INT_FIELD(resultRelIndex);
WRITE_NODE_FIELD(plans);
WRITE_NODE_FIELD(returningLists);
+ WRITE_NODE_FIELD(fdwPrivLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8a51b56dde2..d668128ccb1 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -20,6 +20,7 @@
#include <math.h>
#include "access/skey.h"
+#include "catalog/pg_class.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4695,7 +4696,8 @@ make_result(PlannerInfo *root,
* to make it look better sometime.
*/
ModifyTable *
-make_modifytable(CmdType operation, bool canSetTag,
+make_modifytable(PlannerInfo *root,
+ CmdType operation, bool canSetTag,
List *resultRelations,
List *subplans, List *returningLists,
List *rowMarks, int epqParam)
@@ -4703,7 +4705,10 @@ make_modifytable(CmdType operation, bool canSetTag,
ModifyTable *node = makeNode(ModifyTable);
Plan *plan = &node->plan;
double total_size;
+ List *fdw_private_list;
ListCell *subnode;
+ ListCell *lc;
+ int i;
Assert(list_length(resultRelations) == list_length(subplans));
Assert(returningLists == NIL ||
@@ -4746,6 +4751,53 @@ make_modifytable(CmdType operation, bool canSetTag,
node->rowMarks = rowMarks;
node->epqParam = epqParam;
+ /*
+ * For each result relation that is a foreign table, allow the FDW to
+ * construct private plan data, and accumulate it all into a list.
+ */
+ fdw_private_list = NIL;
+ i = 0;
+ foreach(lc, resultRelations)
+ {
+ Index rti = lfirst_int(lc);
+ FdwRoutine *fdwroutine;
+ List *fdw_private;
+
+ /*
+ * If possible, we want to get the FdwRoutine from our RelOptInfo for
+ * the table. But sometimes we don't have a RelOptInfo and must get
+ * it the hard way. (In INSERT, the target relation is not scanned,
+ * so it's not a baserel; and there are also corner cases for
+ * updatable views where the target rel isn't a baserel.)
+ */
+ if (rti < root->simple_rel_array_size &&
+ root->simple_rel_array[rti] != NULL)
+ {
+ RelOptInfo *resultRel = root->simple_rel_array[rti];
+
+ fdwroutine = resultRel->fdwroutine;
+ }
+ else
+ {
+ RangeTblEntry *rte = planner_rt_fetch(rti, root);
+
+ Assert(rte->rtekind == RTE_RELATION);
+ if (rte->relkind == RELKIND_FOREIGN_TABLE)
+ fdwroutine = GetFdwRoutineByRelId(rte->relid);
+ else
+ fdwroutine = NULL;
+ }
+
+ if (fdwroutine != NULL &&
+ fdwroutine->PlanForeignModify != NULL)
+ fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
+ else
+ fdw_private = NIL;
+ fdw_private_list = lappend(fdw_private_list, fdw_private);
+ i++;
+ }
+ node->fdwPrivLists = fdw_private_list;
+
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index db3d5c50182..0847e787c39 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -571,7 +571,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
else
rowMarks = root->rowMarks;
- plan = (Plan *) make_modifytable(parse->commandType,
+ plan = (Plan *) make_modifytable(root,
+ parse->commandType,
parse->canSetTag,
list_make1_int(parse->resultRelation),
list_make1(plan),
@@ -964,7 +965,8 @@ inheritance_planner(PlannerInfo *root)
rowMarks = root->rowMarks;
/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
- return (Plan *) make_modifytable(parse->commandType,
+ return (Plan *) make_modifytable(root,
+ parse->commandType,
parse->canSetTag,
resultRelations,
subplans,
@@ -2035,6 +2037,15 @@ preprocess_rowmarks(PlannerInfo *root)
if (rte->rtekind != RTE_RELATION)
continue;
+ /*
+ * Similarly, ignore RowMarkClauses for foreign tables; foreign tables
+ * will instead get ROW_MARK_COPY items in the next loop. (FDWs might
+ * choose to do something special while fetching their rows, but that
+ * is of no concern here.)
+ */
+ if (rte->relkind == RELKIND_FOREIGN_TABLE)
+ continue;
+
rels = bms_del_member(rels, rc->rti);
newrc = makeNode(PlanRowMark);
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 7b28b5584b9..fb67f9e4447 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -6,7 +6,8 @@
* For INSERT and UPDATE queries, the targetlist must contain an entry for
* each attribute of the target relation in the correct order. For all query
* types, we may need to add junk tlist entries for Vars used in the RETURNING
- * list and row ID information needed for EvalPlanQual checking.
+ * list and row ID information needed for SELECT FOR UPDATE locking and/or
+ * EvalPlanQual checking.
*
* NOTE: the rewriter's rewriteTargetListIU and rewriteTargetListUD
* routines also do preprocessing of the targetlist. The division of labor
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index d34fca54666..2a943f9c6a5 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2164,7 +2164,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
/*
- * Check for features that are not supported together with FOR [KEY] UPDATE/SHARE.
+ * Check for features that are not supported with FOR [KEY] UPDATE/SHARE.
*
* exported so planner can check again after rewriting, query pullup, etc
*/
@@ -2239,9 +2239,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
switch (rte->rtekind)
{
case RTE_RELATION:
- /* ignore foreign tables */
- if (rte->relkind == RELKIND_FOREIGN_TABLE)
- break;
applyLockingClause(qry, i,
lc->strength, lc->noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
@@ -2251,7 +2248,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
lc->strength, lc->noWait, pushedDown);
/*
- * FOR [KEY] UPDATE/SHARE of subquery is propagated to all of
+ * FOR UPDATE/SHARE of subquery is propagated to all of
* subquery's rels, too. We could do this later (based on
* the marking of the subquery RTE) but it is convenient
* to have local knowledge in each query level about which
@@ -2291,12 +2288,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
switch (rte->rtekind)
{
case RTE_RELATION:
- if (rte->relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("row-level locks cannot be used with foreign table \"%s\"",
- rte->eref->aliasname),
- parser_errposition(pstate, thisrel->location)));
applyLockingClause(qry, i,
lc->strength, lc->noWait,
pushedDown);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 83c83a6a8a4..b99b9930d6a 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -16,6 +16,7 @@
#include "access/sysattr.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
+#include "foreign/fdwapi.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
@@ -1156,6 +1157,7 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
* is a regular table, the junk TLE emits the ctid attribute of the original
* row. When the target relation is a view, there is no ctid, so we instead
* emit a whole-row Var that will contain the "old" values of the view row.
+ * If it's a foreign table, we let the FDW decide what to add.
*
* For UPDATE queries, this is applied after rewriteTargetListIU. The
* ordering isn't actually critical at the moment.
@@ -1183,6 +1185,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
attrname = "ctid";
}
+ else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Let the foreign table's FDW add whatever junk TLEs it wants.
+ */
+ FdwRoutine *fdwroutine;
+
+ fdwroutine = GetFdwRoutineForRelation(target_relation, false);
+
+ if (fdwroutine->AddForeignUpdateTargets != NULL)
+ fdwroutine->AddForeignUpdateTargets(parsetree, target_rte,
+ target_relation);
+
+ return;
+ }
else
{
/*
@@ -1444,17 +1461,13 @@ markQueryForLocking(Query *qry, Node *jtnode,
if (rte->rtekind == RTE_RELATION)
{
- /* ignore foreign tables */
- if (rte->relkind != RELKIND_FOREIGN_TABLE)
- {
- applyLockingClause(qry, rti, strength, noWait, pushedDown);
- rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
- }
+ applyLockingClause(qry, rti, strength, noWait, pushedDown);
+ rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
}
else if (rte->rtekind == RTE_SUBQUERY)
{
applyLockingClause(qry, rti, strength, noWait, pushedDown);
- /* FOR [KEY] UPDATE/SHARE of subquery is propagated to subquery's rels */
+ /* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
strength, noWait, true);
}
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 562d5412df7..485eee320f8 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -38,9 +38,6 @@ typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
List *tlist,
List *scan_clauses);
-typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
- struct ExplainState *es);
-
typedef void (*BeginForeignScan_function) (ForeignScanState *node,
int eflags);
@@ -50,6 +47,48 @@ typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
typedef void (*EndForeignScan_function) (ForeignScanState *node);
+typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
+ RangeTblEntry *target_rte,
+ Relation target_relation);
+
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+ ModifyTable *plan,
+ Index resultRelation,
+ int subplan_index);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo,
+ List *fdw_private,
+ int subplan_index,
+ int eflags);
+
+typedef TupleTableSlot *(*ExecForeignInsert_function) (EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot);
+
+typedef TupleTableSlot *(*ExecForeignUpdate_function) (EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot);
+
+typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot);
+
+typedef void (*EndForeignModify_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
+typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
+ struct ExplainState *es);
+
+typedef void (*ExplainForeignModify_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo,
+ List *fdw_private,
+ int subplan_index,
+ struct ExplainState *es);
+
typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
HeapTuple *rows, int targrows,
double *totalrows,
@@ -73,22 +112,34 @@ typedef struct FdwRoutine
{
NodeTag type;
- /*
- * These functions are required.
- */
+ /* Functions for scanning foreign tables */
GetForeignRelSize_function GetForeignRelSize;
GetForeignPaths_function GetForeignPaths;
GetForeignPlan_function GetForeignPlan;
- ExplainForeignScan_function ExplainForeignScan;
BeginForeignScan_function BeginForeignScan;
IterateForeignScan_function IterateForeignScan;
ReScanForeignScan_function ReScanForeignScan;
EndForeignScan_function EndForeignScan;
/*
- * These functions are optional. Set the pointer to NULL for any that are
- * not provided.
+ * Remaining functions are optional. Set the pointer to NULL for any that
+ * are not provided.
*/
+
+ /* Functions for updating foreign tables */
+ AddForeignUpdateTargets_function AddForeignUpdateTargets;
+ PlanForeignModify_function PlanForeignModify;
+ BeginForeignModify_function BeginForeignModify;
+ ExecForeignInsert_function ExecForeignInsert;
+ ExecForeignUpdate_function ExecForeignUpdate;
+ ExecForeignDelete_function ExecForeignDelete;
+ EndForeignModify_function EndForeignModify;
+
+ /* Support functions for EXPLAIN */
+ ExplainForeignScan_function ExplainForeignScan;
+ ExplainForeignModify_function ExplainForeignModify;
+
+ /* Support functions for ANALYZE */
AnalyzeForeignTable_function AnalyzeForeignTable;
} FdwRoutine;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 76e8cdb1ad8..4f77016652d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -270,7 +270,8 @@ typedef struct ProjectionInfo
* resultSlot: tuple slot used to hold cleaned tuple.
* junkAttNo: not used by junkfilter code. Can be used by caller
* to remember the attno of a specific junk attribute
- * (execMain.c stores the "ctid" attno here).
+ * (nodeModifyTable.c keeps the "ctid" or "wholerow"
+ * attno here).
* ----------------
*/
typedef struct JunkFilter
@@ -300,6 +301,8 @@ typedef struct JunkFilter
* TrigFunctions cached lookup info for trigger functions
* TrigWhenExprs array of trigger WHEN expr states
* TrigInstrument optional runtime measurements for triggers
+ * FdwRoutine FDW callback functions, if foreign table
+ * FdwState available to save private state of FDW
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
* projectReturning for computing a RETURNING list
@@ -317,6 +320,8 @@ typedef struct ResultRelInfo
FmgrInfo *ri_TrigFunctions;
List **ri_TrigWhenExprs;
Instrumentation *ri_TrigInstrument;
+ struct FdwRoutine *ri_FdwRoutine;
+ void *ri_FdwState;
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
ProjectionInfo *ri_projectReturning;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b8b1076bbf..841701ed98a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -173,6 +173,7 @@ typedef struct ModifyTable
int resultRelIndex; /* index of first resultRel in plan's list */
List *plans; /* plan(s) producing source data */
List *returningLists; /* per-target-table RETURNING tlists */
+ List *fdwPrivLists; /* per-target-table FDW private data lists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
} ModifyTable;
@@ -752,13 +753,32 @@ typedef struct Limit
* RowMarkType -
* enums for types of row-marking operations
*
- * When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we have to uniquely
+ * The first four of these values represent different lock strengths that
+ * we can take on tuples according to SELECT FOR [KEY] UPDATE/SHARE requests.
+ * We only support these on regular tables. For foreign tables, any locking
+ * that might be done for these requests must happen during the initial row
+ * fetch; there is no mechanism for going back to lock a row later (and thus
+ * no need for EvalPlanQual machinery during updates of foreign tables).
+ * This means that the semantics will be a bit different than for a local
+ * table; in particular we are likely to lock more rows than would be locked
+ * locally, since remote rows will be locked even if they then fail
+ * locally-checked restriction or join quals. However, the alternative of
+ * doing a separate remote query to lock each selected row is extremely
+ * unappealing, so let's do it like this for now.
+ *
+ * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we have to uniquely
* identify all the source rows, not only those from the target relations, so
* that we can perform EvalPlanQual rechecking at need. For plain tables we
- * can just fetch the TID, the same as for a target relation. Otherwise (for
- * example for VALUES or FUNCTION scans) we have to copy the whole row value.
- * The latter is pretty inefficient but fortunately the case is not
- * performance-critical in practice.
+ * can just fetch the TID, much as for a target relation; this case is
+ * represented by ROW_MARK_REFERENCE. Otherwise (for example for VALUES or
+ * FUNCTION scans) we have to copy the whole row value. ROW_MARK_COPY is
+ * pretty inefficient, since most of the time we'll never need the data; but
+ * fortunately the case is not performance-critical in practice. Note that
+ * we use ROW_MARK_COPY for non-target foreign tables, even if the FDW has a
+ * concept of rowid and so could theoretically support some form of
+ * ROW_MARK_REFERENCE. Although copying the whole row value is inefficient,
+ * it's probably still faster than doing a second remote fetch, so it doesn't
+ * seem worth the extra complexity to permit ROW_MARK_REFERENCE.
*/
typedef enum RowMarkType
{
@@ -776,10 +796,10 @@ typedef enum RowMarkType
* PlanRowMark -
* plan-time representation of FOR [KEY] UPDATE/SHARE clauses
*
- * When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we create a separate
+ * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we create a separate
* PlanRowMark node for each non-target relation in the query. Relations that
- * are not specified as FOR [KEY] UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
- * real tables) or ROW_MARK_COPY (if not).
+ * are not specified as FOR UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
+ * regular tables) or ROW_MARK_COPY (if not).
*
* Initially all PlanRowMarks have rti == prti and isParent == false.
* When the planner discovers that a relation is the root of an inheritance
@@ -791,7 +811,7 @@ typedef enum RowMarkType
*
* The planner also adds resjunk output columns to the plan that carry
* information sufficient to identify the locked or fetched rows. For
- * tables (markType != ROW_MARK_COPY), these columns are named
+ * regular tables (markType != ROW_MARK_COPY), these columns are named
* tableoid%u OID of table
* ctid%u TID of row
* The tableoid column is only present for an inheritance hierarchy.
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 2f9fcd575a6..16d685846e8 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -79,7 +79,8 @@ extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
long numGroups, double outputRows);
extern Result *make_result(PlannerInfo *root, List *tlist,
Node *resconstantqual, Plan *subplan);
-extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
+extern ModifyTable *make_modifytable(PlannerInfo *root,
+ CmdType operation, bool canSetTag,
List *resultRelations, List *subplans, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);