*** a/doc/src/sgml/fdwhandler.sgml --- b/doc/src/sgml/fdwhandler.sgml *************** *** 598,603 **** IsForeignRelUpdatable (Relation rel); --- 598,687 ---- + + FDW Routines For <function>EvalPlanQual</> Checking + + + If an FDW supports EvalPlanQual Checking, it should provide + some or all of the following callback functions depending on + the needs and capabilities of the FDW (see + src/backend/executor/README for details of EvalPlanQual + Checking): + + + + + RowMarkType + GetForeignRowMarkType (LockClauseStrength strength); + + + Report which row-marking option to support for a lock strength + associated with a SELECT FOR UPDATE/SHARE request. + This is called at the beginning of query planning. + strength is a member of the LockClauseStrength + enum type. + The return value should be a member of the RowMarkType + enum type. See + src/include/nodes/lockoptions.h and + src/include/nodes/plannodes.h for information about + these enum types. + + + + If the GetForeignRowMarkType pointer is set to + NULL, the default option is selected for any lock strength, + and both LockForeignRow and FetchForeignRow + described below will not be called at query execution time. + + + + + bool + LockForeignRow (EState *estate, + ExecRowMark *erm, + ItemPointer tupleid); + + + Lock one tuple in the foreign table. + estate is global execution state for the query. + erm is the ExecRowMark struct describing + the target foreign table. + tupleid identifies the tuple to be locked. + This function should return true, if the FDW lock the tuple + successfully. Otherwise, return false. + + + + If the LockForeignRow pointer is set to + NULL, attempts to lock the tuple will fail + with an error message. + + + + + HeapTuple + FetchForeignRow (EState *estate, + ExecRowMark *erm, + ItemPointer tupleid); + + + Fetch one tuple from the foreign table. + estate is global execution state for the query. + erm is the ExecRowMark struct describing + the target foreign table. + tupleid identifies the tuple to be fetched. + This function should return the fetched tuple, if the FDW fetch the + tuple successfully. Otherwise, return NULL. + + + + If the FetchForeignRow pointer is set to + NULL, attempts to fetch the tuple will fail + with an error message. + + + + FDW Routines for <command>EXPLAIN</> *************** *** 1011,1017 **** GetForeignServerByName(const char *name, bool missing_ok); join conditions. However, matching the local semantics exactly would require an additional remote access for every row, and might be impossible anyway depending on what locking semantics the external data ! source provides. --- 1095,1104 ---- join conditions. However, matching the local semantics exactly would require an additional remote access for every row, and might be impossible anyway depending on what locking semantics the external data ! source provides. If necessary, however, the FDW can change this behavior ! so as to match the local semantics, using its callback functions ! GetForeignRowMarkType, LockForeignRow, and ! FetchForeignRow. *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 855,860 **** InitPlan(QueryDesc *queryDesc, int eflags) --- 855,861 ---- erm->markType = rc->markType; erm->waitPolicy = rc->waitPolicy; ItemPointerSetInvalid(&(erm->curCtid)); + erm->fdw_state = NULL; estate->es_rowMarks = lappend(estate->es_rowMarks, erm); } *************** *** 1098,1103 **** CheckValidResultRel(Relation resultRel, CmdType operation) --- 1099,1106 ---- static void CheckValidRowMarkRel(Relation rel, RowMarkType markType) { + FdwRoutine *fdwroutine; + switch (rel->rd_rel->relkind) { case RELKIND_RELATION: *************** *** 1133,1143 **** CheckValidRowMarkRel(Relation rel, RowMarkType markType) RelationGetRelationName(rel)))); break; case RELKIND_FOREIGN_TABLE: ! /* Should not get here; planner should have used ROW_MARK_COPY */ ! ereport(ERROR, ! (errcode(ERRCODE_WRONG_OBJECT_TYPE), ! errmsg("cannot lock rows in foreign table \"%s\"", ! RelationGetRelationName(rel)))); break; default: ereport(ERROR, --- 1136,1150 ---- RelationGetRelationName(rel)))); break; case RELKIND_FOREIGN_TABLE: ! /* Okay only if the FDW supports it */ ! fdwroutine = GetFdwRoutineForRelation(rel, false); ! if ((markType != ROW_MARK_REFERENCE && ! fdwroutine->LockForeignRow == NULL) || ! fdwroutine->FetchForeignRow == NULL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot lock rows in foreign table \"%s\"", ! RelationGetRelationName(rel)))); break; default: ereport(ERROR, *************** *** 2401,2407 **** EvalPlanQualFetchRowMarks(EPQState *epqstate) if (erm->markType == ROW_MARK_REFERENCE) { ! Buffer buffer; Assert(erm->relation != NULL); --- 2408,2414 ---- if (erm->markType == ROW_MARK_REFERENCE) { ! HeapTuple copyTuple; Assert(erm->relation != NULL); *************** *** 2415,2428 **** EvalPlanQualFetchRowMarks(EPQState *epqstate) tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); /* okay, fetch the tuple */ - if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, - false, NULL)) - elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); ! /* successful, copy and store tuple */ ! EvalPlanQualSetTuple(epqstate, erm->rti, ! heap_copytuple(&tuple)); ! ReleaseBuffer(buffer); } else { --- 2422,2454 ---- tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); /* okay, fetch the tuple */ ! if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ! { ! FdwRoutine *fdwroutine; ! ! /* let the FDW do the work */ ! fdwroutine = GetFdwRoutineForRelation(erm->relation, false); ! copyTuple = fdwroutine->FetchForeignRow(epqstate->estate, ! erm, &tuple.t_self); ! if (!copyTuple) ! elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); ! } ! else ! { ! Buffer buffer; ! ! if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, ! false, NULL)) ! elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); ! ! /* successful, copy tuple */ ! copyTuple = heap_copytuple(&tuple); ! ReleaseBuffer(buffer); ! } ! ! /* store tuple */ ! EvalPlanQualSetTuple(epqstate, erm->rti, copyTuple); } else { *** a/src/backend/executor/nodeLockRows.c --- b/src/backend/executor/nodeLockRows.c *************** *** 25,30 **** --- 25,31 ---- #include "access/xact.h" #include "executor/executor.h" #include "executor/nodeLockRows.h" + #include "foreign/fdwapi.h" #include "storage/bufmgr.h" #include "utils/rel.h" #include "utils/tqual.h" *************** *** 112,117 **** lnext: --- 113,138 ---- tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); /* okay, try to lock the tuple */ + + if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + FdwRoutine *fdwroutine; + + /* Let the FDW do the work */ + fdwroutine = GetFdwRoutineForRelation(erm->relation, false); + if (!fdwroutine->LockForeignRow(estate, erm, &tuple.t_self)) + { + /* couldn't get the lock */ + goto lnext; + } + /* got the lock successfully */ + + /* Remember locked tuple's TID for EvalPlanQual testing */ + erm->curCtid = tuple.t_self; + + continue; + } + switch (erm->markType) { case ROW_MARK_EXCLUSIVE: *************** *** 253,259 **** lnext: ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc); ExecRowMark *erm = aerm->rowmark; HeapTupleData tuple; ! Buffer buffer; /* ignore non-active child tables */ if (!ItemPointerIsValid(&(erm->curCtid))) --- 274,280 ---- ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc); ExecRowMark *erm = aerm->rowmark; HeapTupleData tuple; ! HeapTuple copyTuple; /* ignore non-active child tables */ if (!ItemPointerIsValid(&(erm->curCtid))) *************** *** 267,280 **** lnext: /* okay, fetch the tuple */ tuple.t_self = erm->curCtid; - if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, - false, NULL)) - elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); ! /* successful, copy and store tuple */ ! EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, ! heap_copytuple(&tuple)); ! ReleaseBuffer(buffer); } /* --- 288,320 ---- /* okay, fetch the tuple */ tuple.t_self = erm->curCtid; ! if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ! { ! FdwRoutine *fdwroutine; ! ! /* let the FDW do the work */ ! fdwroutine = GetFdwRoutineForRelation(erm->relation, false); ! copyTuple = fdwroutine->FetchForeignRow(node->lr_epqstate.estate, ! erm, &tuple.t_self); ! if (!copyTuple) ! elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); ! } ! else ! { ! Buffer buffer; ! ! if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, ! false, NULL)) ! elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); ! ! /* successful, copy tuple */ ! copyTuple = heap_copytuple(&tuple); ! ReleaseBuffer(buffer); ! } ! ! /* store tuple */ ! EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, copyTuple); } /* *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** *** 20,25 **** --- 20,26 ---- #include "access/htup_details.h" #include "executor/executor.h" #include "executor/nodeAgg.h" + #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #ifdef OPTIMIZER_DEBUG *************** *** 2274,2280 **** select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength) } else if (rte->relkind == RELKIND_FOREIGN_TABLE) { ! /* For now, we force all foreign tables to use ROW_MARK_COPY */ return ROW_MARK_COPY; } else --- 2275,2288 ---- } else if (rte->relkind == RELKIND_FOREIGN_TABLE) { ! FdwRoutine *fdwroutine; ! ! /* Let the FDW select the rowmark type, if possible */ ! fdwroutine = GetFdwRoutineByRelId(rte->relid); ! if (fdwroutine->GetForeignRowMarkType != NULL) ! return fdwroutine->GetForeignRowMarkType(strength); ! ! /* Otherwise, we force all foreign tables to use ROW_MARK_COPY */ return ROW_MARK_COPY; } else *** a/src/include/foreign/fdwapi.h --- b/src/include/foreign/fdwapi.h *************** *** 13,18 **** --- 13,19 ---- #define FDWAPI_H #include "nodes/execnodes.h" + #include "nodes/plannodes.h" #include "nodes/relation.h" /* To avoid including explain.h here, reference ExplainState thus: */ *************** *** 82,87 **** typedef void (*EndForeignModify_function) (EState *estate, --- 83,98 ---- typedef int (*IsForeignRelUpdatable_function) (Relation rel); + typedef RowMarkType (*GetForeignRowMarkType_function) (LockClauseStrength strength); + + typedef bool (*LockForeignRow_function) (EState *estate, + ExecRowMark *erm, + ItemPointer tupleid); + + typedef HeapTuple (*FetchForeignRow_function) (EState *estate, + ExecRowMark *erm, + ItemPointer tupleid); + typedef void (*ExplainForeignScan_function) (ForeignScanState *node, struct ExplainState *es); *************** *** 141,146 **** typedef struct FdwRoutine --- 152,162 ---- EndForeignModify_function EndForeignModify; IsForeignRelUpdatable_function IsForeignRelUpdatable; + /* Functions for EvalPlanQual rechecking */ + GetForeignRowMarkType_function GetForeignRowMarkType; + LockForeignRow_function LockForeignRow; + FetchForeignRow_function FetchForeignRow; + /* Support functions for EXPLAIN */ ExplainForeignScan_function ExplainForeignScan; ExplainForeignModify_function ExplainForeignModify; *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 420,426 **** typedef struct EState * subqueries-in-FROM will have an ExecRowMark with relation == NULL. See * PlanRowMark for details about most of the fields. In addition to fields * directly derived from PlanRowMark, we store curCtid, which is used by the ! * WHERE CURRENT OF code. * * EState->es_rowMarks is a list of these structs. */ --- 420,426 ---- * subqueries-in-FROM will have an ExecRowMark with relation == NULL. See * PlanRowMark for details about most of the fields. In addition to fields * directly derived from PlanRowMark, we store curCtid, which is used by the ! * WHERE CURRENT OF code, and fdw_state, which is used by the FDW. * * EState->es_rowMarks is a list of these structs. */ *************** *** 434,439 **** typedef struct ExecRowMark --- 434,440 ---- RowMarkType markType; /* see enum in nodes/plannodes.h */ LockWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED */ ItemPointerData curCtid; /* ctid of currently locked tuple, if any */ + void *fdw_state; /* foreign-data wrapper can keep state here */ } ExecRowMark; /*