diff options
author | Robert Haas <rhaas@postgresql.org> | 2015-12-08 12:31:03 -0500 |
---|---|---|
committer | Robert Haas <rhaas@postgresql.org> | 2015-12-08 12:54:33 -0500 |
commit | 325b357bc255149c5ace7d77f5c15c941bafef0a (patch) | |
tree | 46ec7eba83520a4746b584b1065805682507c9bd /src/backend/executor | |
parent | 25517ee14c1a018876b64dce73e8f7fb7e937783 (diff) | |
download | postgresql-325b357bc255149c5ace7d77f5c15c941bafef0a.tar.gz postgresql-325b357bc255149c5ace7d77f5c15c941bafef0a.zip |
Allow foreign and custom joins to handle EvalPlanQual rechecks.
Commit e7cb7ee14555cc9c5773e2c102efd6371f6f2005 provided basic
infrastructure for allowing a foreign data wrapper or custom scan
provider to replace a join of one or more tables with a scan.
However, this infrastructure failed to take into account the need
for possible EvalPlanQual rechecks, and ExecScanFetch would fail
an assertion (or just overwrite memory) if such a check was attempted
for a plan containing a pushed-down join. To fix, adjust the EPQ
machinery to skip some processing steps when scanrelid == 0, making
those the responsibility of scan's recheck method, which also has
the responsibility in this case of correctly populating the relevant
slot.
To allow foreign scans to gain control in the right place to make
use of this new facility, add a new, optional RecheckForeignScan
method. Also, allow a foreign scan to have a child plan, which can
be used to correctly populate the slot (or perhaps for something
else, but this is the only use currently envisioned).
KaiGai Kohei, reviewed by Robert Haas, Etsuro Fujita, and Kyotaro
Horiguchi.
Diffstat (limited to 'src/backend/executor')
-rw-r--r-- | src/backend/executor/execScan.c | 44 | ||||
-rw-r--r-- | src/backend/executor/nodeForeignscan.c | 32 |
2 files changed, 72 insertions, 4 deletions
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index a96e826ba42..3faf7f9a77d 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -49,8 +49,21 @@ ExecScanFetch(ScanState *node, */ Index scanrelid = ((Scan *) node->ps.plan)->scanrelid; - Assert(scanrelid > 0); - if (estate->es_epqTupleSet[scanrelid - 1]) + if (scanrelid == 0) + { + TupleTableSlot *slot = node->ss_ScanTupleSlot; + + /* + * This is a ForeignScan or CustomScan which has pushed down a + * join to the remote side. The recheck method is responsible not + * only for rechecking the scan/join quals but also for storing + * the correct tuple in the slot. + */ + if (!(*recheckMtd) (node, slot)) + ExecClearTuple(slot); /* would not be returned by scan */ + return slot; + } + else if (estate->es_epqTupleSet[scanrelid - 1]) { TupleTableSlot *slot = node->ss_ScanTupleSlot; @@ -347,8 +360,31 @@ ExecScanReScan(ScanState *node) { Index scanrelid = ((Scan *) node->ps.plan)->scanrelid; - Assert(scanrelid > 0); + if (scanrelid > 0) + estate->es_epqScanDone[scanrelid - 1] = false; + else + { + Bitmapset *relids; + int rtindex = -1; - estate->es_epqScanDone[scanrelid - 1] = false; + /* + * If an FDW or custom scan provider has replaced the join with a + * scan, there are multiple RTIs; reset the epqScanDone flag for + * all of them. + */ + if (IsA(node->ps.plan, ForeignScan)) + relids = ((ForeignScan *) node->ps.plan)->fs_relids; + else if (IsA(node->ps.plan, CustomScan)) + relids = ((CustomScan *) node->ps.plan)->custom_relids; + else + elog(ERROR, "unexpected scan node: %d", + (int) nodeTag(node->ps.plan)); + + while ((rtindex = bms_next_member(relids, rtindex)) >= 0) + { + Assert(rtindex > 0); + estate->es_epqScanDone[rtindex - 1] = false; + } + } } } diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c index 6165e4a6cb4..62959e3a649 100644 --- a/src/backend/executor/nodeForeignscan.c +++ b/src/backend/executor/nodeForeignscan.c @@ -73,6 +73,7 @@ ForeignNext(ForeignScanState *node) static bool ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot) { + FdwRoutine *fdwroutine = node->fdwroutine; ExprContext *econtext; /* @@ -85,6 +86,18 @@ ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot) ResetExprContext(econtext); + /* + * If an outer join is pushed down, RecheckForeignScan may need to store a + * different tuple in the slot, because a different set of columns may go + * to NULL upon recheck. Otherwise, it shouldn't need to change the slot + * contents, just return true or false to indicate whether the quals still + * pass. For simple cases, setting fdw_recheck_quals may be easier than + * providing this callback. + */ + if (fdwroutine->RecheckForeignScan && + !fdwroutine->RecheckForeignScan(node, slot)) + return false; + return ExecQual(node->fdw_recheck_quals, econtext, false); } @@ -205,6 +218,11 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) scanstate->fdwroutine = fdwroutine; scanstate->fdw_state = NULL; + /* Initialize any outer plan. */ + if (outerPlan(node)) + outerPlanState(scanstate) = + ExecInitNode(outerPlan(node), estate, eflags); + /* * Tell the FDW to initialize the scan. */ @@ -225,6 +243,10 @@ ExecEndForeignScan(ForeignScanState *node) /* Let the FDW shut down */ node->fdwroutine->EndForeignScan(node); + /* Shut down any outer plan. */ + if (outerPlanState(node)) + ExecEndNode(outerPlanState(node)); + /* Free the exprcontext */ ExecFreeExprContext(&node->ss.ps); @@ -246,7 +268,17 @@ ExecEndForeignScan(ForeignScanState *node) void ExecReScanForeignScan(ForeignScanState *node) { + PlanState *outerPlan = outerPlanState(node); + node->fdwroutine->ReScanForeignScan(node); + /* + * If chgParam of subnode is not null then plan will be re-scanned by + * first ExecProcNode. outerPlan may also be NULL, in which case there + * is nothing to rescan at all. + */ + if (outerPlan != NULL && outerPlan->chgParam == NULL) + ExecReScan(outerPlan); + ExecScanReScan(&node->ss); } |