*** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** *** 887,893 **** ExplainNode(PlanState *planstate, List *ancestors, pname = sname = "WorkTable Scan"; break; case T_ForeignScan: ! pname = sname = "Foreign Scan"; break; case T_CustomScan: sname = "Custom Scan"; --- 887,911 ---- pname = sname = "WorkTable Scan"; break; case T_ForeignScan: ! sname = "Foreign Scan"; ! switch (((ForeignScan *) plan)->operation) ! { ! case CMD_SELECT: ! pname = "Foreign Scan"; ! operation = "Select"; ! break; ! case CMD_UPDATE: ! pname = "Foreign Update"; ! operation = "Update"; ! break; ! case CMD_DELETE: ! pname = "Foreign Delete"; ! operation = "Delete"; ! break; ! default: ! pname = "???"; ! break; ! } break; case T_CustomScan: sname = "Custom Scan"; *************** *** 1658,1663 **** show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es) --- 1676,1686 ---- return; if (IsA(plan, RecursiveUnion)) return; + /* Likewise for ForeignScan in case of pushed-down UPDATE/DELETE */ + if (IsA(plan, ForeignScan) && + (((ForeignScan *) plan)->operation == CMD_UPDATE || + ((ForeignScan *) plan)->operation == CMD_DELETE)) + return; /* Set up deparsing context */ context = set_deparse_context_planstate(es->deparse_cxt, *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 1011,1020 **** InitPlan(QueryDesc *queryDesc, int eflags) * CheckValidRowMarkRel. */ void ! CheckValidResultRel(Relation resultRel, CmdType operation) { TriggerDesc *trigDesc = resultRel->trigdesc; FdwRoutine *fdwroutine; switch (resultRel->rd_rel->relkind) { --- 1011,1022 ---- * CheckValidRowMarkRel. */ void ! CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation) { + Relation resultRel = resultRelInfo->ri_RelationDesc; TriggerDesc *trigDesc = resultRel->trigdesc; FdwRoutine *fdwroutine; + bool supported; switch (resultRel->rd_rel->relkind) { *************** *** 1083,1096 **** CheckValidResultRel(Relation resultRel, CmdType operation) case RELKIND_FOREIGN_TABLE: /* 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)))); if (fdwroutine->IsForeignRelUpdatable != NULL && (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0) ereport(ERROR, --- 1085,1107 ---- case RELKIND_FOREIGN_TABLE: /* Okay only if the FDW supports it */ fdwroutine = GetFdwRoutineForRelation(resultRel, false); + supported = ((fdwroutine->BeginDMLPushdown != NULL) && + (fdwroutine->IterateDMLPushdown != NULL) && + (fdwroutine->EndDMLPushdown != NULL)); switch (operation) { case CMD_INSERT: ! if (resultRelInfo->ri_FdwPushdown && !supported) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot push down insert on foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); ! if (!resultRelInfo->ri_FdwPushdown && ! 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, *************** *** 1099,1105 **** CheckValidResultRel(Relation resultRel, CmdType operation) RelationGetRelationName(resultRel)))); break; case CMD_UPDATE: ! if (fdwroutine->ExecForeignUpdate == NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot update foreign table \"%s\"", --- 1110,1122 ---- RelationGetRelationName(resultRel)))); break; case CMD_UPDATE: ! if (resultRelInfo->ri_FdwPushdown && !supported) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot push down update on foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); ! if (!resultRelInfo->ri_FdwPushdown && ! fdwroutine->ExecForeignUpdate == NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot update foreign table \"%s\"", *************** *** 1112,1118 **** CheckValidResultRel(Relation resultRel, CmdType operation) RelationGetRelationName(resultRel)))); break; case CMD_DELETE: ! if (fdwroutine->ExecForeignDelete == NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot delete from foreign table \"%s\"", --- 1129,1141 ---- RelationGetRelationName(resultRel)))); break; case CMD_DELETE: ! if (resultRelInfo->ri_FdwPushdown && !supported) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot push down delete on foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); ! if (!resultRelInfo->ri_FdwPushdown && ! fdwroutine->ExecForeignDelete == NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot delete from foreign table \"%s\"", *************** *** 1245,1250 **** InitResultRelInfo(ResultRelInfo *resultRelInfo, --- 1268,1274 ---- else resultRelInfo->ri_FdwRoutine = NULL; resultRelInfo->ri_FdwState = NULL; + resultRelInfo->ri_FdwPushdown = false; resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_junkFilter = NULL; resultRelInfo->ri_projectReturning = NULL; *** a/src/backend/executor/nodeForeignscan.c --- b/src/backend/executor/nodeForeignscan.c *************** *** 48,54 **** ForeignNext(ForeignScanState *node) /* Call the Iterate function in short-lived context */ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); ! slot = node->fdwroutine->IterateForeignScan(node); MemoryContextSwitchTo(oldcontext); /* --- 48,57 ---- /* Call the Iterate function in short-lived context */ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); ! if (plan->operation != CMD_SELECT) ! slot = node->fdwroutine->IterateDMLPushdown(node); ! else ! slot = node->fdwroutine->IterateForeignScan(node); MemoryContextSwitchTo(oldcontext); /* *************** *** 226,232 **** ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) /* * Tell the FDW to initialize the scan. */ ! fdwroutine->BeginForeignScan(scanstate, eflags); return scanstate; } --- 229,238 ---- /* * Tell the FDW to initialize the scan. */ ! if (node->operation != CMD_SELECT) ! fdwroutine->BeginDMLPushdown(scanstate, eflags); ! else ! fdwroutine->BeginForeignScan(scanstate, eflags); return scanstate; } *************** *** 240,247 **** ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) void ExecEndForeignScan(ForeignScanState *node) { /* Let the FDW shut down */ ! node->fdwroutine->EndForeignScan(node); /* Shut down any outer plan. */ if (outerPlanState(node)) --- 246,258 ---- void ExecEndForeignScan(ForeignScanState *node) { + ForeignScan *plan = (ForeignScan *) node->ss.ps.plan; + /* Let the FDW shut down */ ! if (plan->operation != CMD_SELECT) ! node->fdwroutine->EndDMLPushdown(node); ! else ! node->fdwroutine->EndForeignScan(node); /* Shut down any outer plan. */ if (outerPlanState(node)) *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 1348,1353 **** ExecModifyTable(ModifyTableState *node) --- 1348,1359 ---- break; } + if (resultRelInfo->ri_FdwPushdown) + { + estate->es_result_relation_info = saved_resultRelInfo; + return planSlot; + } + EvalPlanQualSetSlot(&node->mt_epqstate, planSlot); slot = planSlot; *************** *** 1527,1536 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) { subplan = (Plan *) lfirst(l); /* * Verify result relation is a valid target for the current operation */ ! CheckValidResultRel(resultRelInfo->ri_RelationDesc, operation); /* * If there are indices on the result relation, open them and save --- 1533,1545 ---- { subplan = (Plan *) lfirst(l); + /* Initialize the FdwPushdown flag */ + resultRelInfo->ri_FdwPushdown = list_nth_int(node->fdwPushdowns, i); + /* * Verify result relation is a valid target for the current operation */ ! CheckValidResultRel(resultRelInfo, operation); /* * If there are indices on the result relation, open them and save *************** *** 1551,1557 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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); --- 1560,1567 ---- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags); /* Also let FDWs init themselves for foreign-table result rels */ ! if (resultRelInfo->ri_FdwPushdown == false && ! resultRelInfo->ri_FdwRoutine != NULL && resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL) { List *fdw_private = (List *) list_nth(node->fdwPrivLists, i); *************** *** 1722,1734 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) --- 1732,1753 ---- erm = ExecFindRowMark(estate, rc->rti, false); /* build ExecAuxRowMark for each subplan */ + resultRelInfo = mtstate->resultRelInfo; for (i = 0; i < nplans; i++) { ExecAuxRowMark *aerm; + /* ignore subplan if the FDW pushes the statement down */ + if (resultRelInfo->ri_FdwPushdown) + { + resultRelInfo++; + continue; + } + subplan = mtstate->mt_plans[i]->plan; aerm = ExecBuildAuxRowMark(erm, subplan->targetlist); mtstate->mt_arowmarks[i] = lappend(mtstate->mt_arowmarks[i], aerm); + resultRelInfo++; } } *************** *** 1784,1789 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) --- 1803,1815 ---- { JunkFilter *j; + /* ignore subplan if the FDW pushes the statement down */ + if (resultRelInfo->ri_FdwPushdown) + { + resultRelInfo++; + continue; + } + subplan = mtstate->mt_plans[i]->plan; if (operation == CMD_INSERT || operation == CMD_UPDATE) ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, *************** *** 1878,1884 **** ExecEndModifyTable(ModifyTableState *node) { ResultRelInfo *resultRelInfo = node->resultRelInfo + i; ! if (resultRelInfo->ri_FdwRoutine != NULL && resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state, resultRelInfo); --- 1904,1911 ---- { ResultRelInfo *resultRelInfo = node->resultRelInfo + i; ! if (resultRelInfo->ri_FdwPushdown == false && ! resultRelInfo->ri_FdwRoutine != NULL && resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state, resultRelInfo); *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 185,190 **** _copyModifyTable(const ModifyTable *from) --- 185,191 ---- COPY_NODE_FIELD(plans); COPY_NODE_FIELD(withCheckOptionLists); COPY_NODE_FIELD(returningLists); + COPY_NODE_FIELD(fdwPushdowns); COPY_NODE_FIELD(fdwPrivLists); COPY_NODE_FIELD(rowMarks); COPY_SCALAR_FIELD(epqParam); *************** *** 645,650 **** _copyForeignScan(const ForeignScan *from) --- 646,652 ---- /* * copy remainder of node */ + COPY_SCALAR_FIELD(operation); COPY_SCALAR_FIELD(fs_server); COPY_NODE_FIELD(fdw_exprs); COPY_NODE_FIELD(fdw_private); *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 339,344 **** _outModifyTable(StringInfo str, const ModifyTable *node) --- 339,345 ---- WRITE_NODE_FIELD(plans); WRITE_NODE_FIELD(withCheckOptionLists); WRITE_NODE_FIELD(returningLists); + WRITE_NODE_FIELD(fdwPushdowns); WRITE_NODE_FIELD(fdwPrivLists); WRITE_NODE_FIELD(rowMarks); WRITE_INT_FIELD(epqParam); *************** *** 591,596 **** _outForeignScan(StringInfo str, const ForeignScan *node) --- 592,598 ---- _outScanInfo(str, (const Scan *) node); + WRITE_ENUM_FIELD(operation, CmdType); WRITE_OID_FIELD(fs_server); WRITE_NODE_FIELD(fdw_exprs); WRITE_NODE_FIELD(fdw_private); *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 1470,1475 **** _readModifyTable(void) --- 1470,1476 ---- READ_NODE_FIELD(plans); READ_NODE_FIELD(withCheckOptionLists); READ_NODE_FIELD(returningLists); + READ_NODE_FIELD(fdwPushdowns); READ_NODE_FIELD(fdwPrivLists); READ_NODE_FIELD(rowMarks); READ_INT_FIELD(epqParam); *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 3765,3770 **** make_foreignscan(List *qptlist, --- 3765,3771 ---- plan->lefttree = outer_plan; plan->righttree = NULL; node->scan.scanrelid = scanrelid; + node->operation = CMD_SELECT; /* fs_server will be filled in by create_foreignscan_plan */ node->fs_server = InvalidOid; node->fdw_exprs = fdw_exprs; *************** *** 5039,5044 **** make_modifytable(PlannerInfo *root, --- 5040,5046 ---- ModifyTable *node = makeNode(ModifyTable); Plan *plan = &node->plan; double total_size; + List *fdwpushdown_list; List *fdw_private_list; ListCell *subnode; ListCell *lc; *************** *** 5119,5130 **** make_modifytable(PlannerInfo *root, --- 5121,5134 ---- * For each result relation that is a foreign table, allow the FDW to * construct private plan data, and accumulate it all into a list. */ + fdwpushdown_list = NIL; fdw_private_list = NIL; i = 0; foreach(lc, resultRelations) { Index rti = lfirst_int(lc); FdwRoutine *fdwroutine; + bool fdwpushdown; List *fdw_private; /* *************** *** 5153,5158 **** make_modifytable(PlannerInfo *root, --- 5157,5170 ---- } if (fdwroutine != NULL && + fdwroutine->PlanDMLPushdown != NULL) + fdwpushdown = fdwroutine->PlanDMLPushdown(root, node, rti, i); + else + fdwpushdown = false; + fdwpushdown_list = lappend_int(fdwpushdown_list, fdwpushdown); + + if (!fdwpushdown && + fdwroutine != NULL && fdwroutine->PlanForeignModify != NULL) fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i); else *************** *** 5160,5165 **** make_modifytable(PlannerInfo *root, --- 5172,5178 ---- fdw_private_list = lappend(fdw_private_list, fdw_private); i++; } + node->fdwPushdowns = fdwpushdown_list; node->fdwPrivLists = fdw_private_list; return node; *** a/src/include/executor/executor.h --- b/src/include/executor/executor.h *************** *** 184,190 **** extern void ExecutorEnd(QueryDesc *queryDesc); extern void standard_ExecutorEnd(QueryDesc *queryDesc); extern void ExecutorRewind(QueryDesc *queryDesc); extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation); ! extern void CheckValidResultRel(Relation resultRel, CmdType operation); extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, --- 184,190 ---- extern void standard_ExecutorEnd(QueryDesc *queryDesc); extern void ExecutorRewind(QueryDesc *queryDesc); extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation); ! extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation); extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, *** a/src/include/foreign/fdwapi.h --- b/src/include/foreign/fdwapi.h *************** *** 91,96 **** typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate, --- 91,108 ---- typedef void (*EndForeignModify_function) (EState *estate, ResultRelInfo *rinfo); + typedef bool (*PlanDMLPushdown_function) (PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + int subplan_index); + + typedef void (*BeginDMLPushdown_function) (ForeignScanState *node, + int eflags); + + typedef TupleTableSlot *(*IterateDMLPushdown_function) (ForeignScanState *node); + + typedef void (*EndDMLPushdown_function) (ForeignScanState *node); + typedef int (*IsForeignRelUpdatable_function) (Relation rel); typedef RowMarkType (*GetForeignRowMarkType_function) (RangeTblEntry *rte, *************** *** 161,166 **** typedef struct FdwRoutine --- 173,182 ---- ExecForeignUpdate_function ExecForeignUpdate; ExecForeignDelete_function ExecForeignDelete; EndForeignModify_function EndForeignModify; + PlanDMLPushdown_function PlanDMLPushdown; + BeginDMLPushdown_function BeginDMLPushdown; + IterateDMLPushdown_function IterateDMLPushdown; + EndDMLPushdown_function EndDMLPushdown; IsForeignRelUpdatable_function IsForeignRelUpdatable; /* Functions for SELECT FOR UPDATE/SHARE row locking */ *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 311,316 **** typedef struct JunkFilter --- 311,317 ---- * TrigInstrument optional runtime measurements for triggers * FdwRoutine FDW callback functions, if foreign table * FdwState available to save private state of FDW + * FdwPushdown true when the command is pushed down * WithCheckOptions list of WithCheckOption's to be checked * WithCheckOptionExprs list of WithCheckOption expr states * ConstraintExprs array of constraint-checking expr states *************** *** 334,339 **** typedef struct ResultRelInfo --- 335,341 ---- Instrumentation *ri_TrigInstrument; struct FdwRoutine *ri_FdwRoutine; void *ri_FdwState; + bool ri_FdwPushdown; List *ri_WithCheckOptions; List *ri_WithCheckOptionExprs; List **ri_ConstraintExprs; *** a/src/include/nodes/plannodes.h --- b/src/include/nodes/plannodes.h *************** *** 187,192 **** typedef struct ModifyTable --- 187,193 ---- List *plans; /* plan(s) producing source data */ List *withCheckOptionLists; /* per-target-table WCO lists */ List *returningLists; /* per-target-table RETURNING tlists */ + List *fdwPushdowns; /* per-target-table FDW pushdown flags */ List *fdwPrivLists; /* per-target-table FDW private data lists */ List *rowMarks; /* PlanRowMarks (non-locking only) */ int epqParam; /* ID of Param for EvalPlanQual re-eval */ *************** *** 530,535 **** typedef struct WorkTableScan --- 531,537 ---- typedef struct ForeignScan { Scan scan; + CmdType operation; /* SELECT/UPDATE/DELETE */ Oid fs_server; /* OID of foreign server */ List *fdw_exprs; /* expressions that FDW may evaluate */ List *fdw_private; /* private data for FDW */