diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/commands/copy.c | 15 | ||||
-rw-r--r-- | src/backend/commands/explain.c | 35 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 42 | ||||
-rw-r--r-- | src/backend/executor/nodeForeignscan.c | 3 | ||||
-rw-r--r-- | src/backend/executor/nodeModifyTable.c | 151 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 1 | ||||
-rw-r--r-- | src/backend/nodes/outfuncs.c | 1 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 54 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 15 | ||||
-rw-r--r-- | src/backend/optimizer/prep/preptlist.c | 3 | ||||
-rw-r--r-- | src/backend/parser/analyze.c | 13 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 27 | ||||
-rw-r--r-- | src/include/foreign/fdwapi.h | 69 | ||||
-rw-r--r-- | src/include/nodes/execnodes.h | 7 | ||||
-rw-r--r-- | src/include/nodes/plannodes.h | 38 | ||||
-rw-r--r-- | src/include/optimizer/planmain.h | 3 |
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); |