/*------------------------------------------------------------------------- * * execScan.c * This code provides support for generalized relation scans. ExecScan * is passed a node and a pointer to a function to "do the right thing" * and return a tuple from the relation. ExecScan then does the tedious * stuff - checking the qualification and projecting the tuple * appropriately. * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/executor/execScan.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "executor/executor.h" #include "miscadmin.h" #include "utils/memutils.h" /* * ExecScanFetch -- check interrupts & fetch next potential tuple * * This routine is concerned with substituting a test tuple if we are * inside an EvalPlanQual recheck. If we aren't, just execute * the access method's next-tuple routine. */ static inline TupleTableSlot * ExecScanFetch(ScanState *node, ExecScanAccessMtd accessMtd, ExecScanRecheckMtd recheckMtd) { EState *estate = node->ps.state; CHECK_FOR_INTERRUPTS(); if (estate->es_epq_active != NULL) { EPQState *epqstate = estate->es_epq_active; /* * We are inside an EvalPlanQual recheck. Return the test tuple if * one is available, after rechecking any access-method-specific * conditions. */ Index scanrelid = ((Scan *) node->ps.plan)->scanrelid; if (scanrelid == 0) { /* * 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. */ TupleTableSlot *slot = node->ss_ScanTupleSlot; if (!(*recheckMtd) (node, slot)) ExecClearTuple(slot); /* would not be returned by scan */ return slot; } else if (epqstate->relsubs_done[scanrelid - 1]) { /* * Return empty slot, as either there is no EPQ tuple for this rel * or we already returned it. */ TupleTableSlot *slot = node->ss_ScanTupleSlot; return ExecClearTuple(slot); } else if (epqstate->relsubs_slot[scanrelid - 1] != NULL) { /* * Return replacement tuple provided by the EPQ caller. */ TupleTableSlot *slot = epqstate->relsubs_slot[scanrelid - 1]; Assert(epqstate->relsubs_rowmark[scanrelid - 1] == NULL); /* Mark to remember that we shouldn't return it again */ epqstate->relsubs_done[scanrelid - 1] = true; /* Return empty slot if we haven't got a test tuple */ if (TupIsNull(slot)) return NULL; /* Check if it meets the access-method conditions */ if (!(*recheckMtd) (node, slot)) return ExecClearTuple(slot); /* would not be returned by * scan */ return slot; } else if (epqstate->relsubs_rowmark[scanrelid - 1] != NULL) { /* * Fetch and return replacement tuple using a non-locking rowmark. */ TupleTableSlot *slot = node->ss_ScanTupleSlot; /* Mark to remember that we shouldn't return more */ epqstate->relsubs_done[scanrelid - 1] = true; if (!EvalPlanQualFetchRowMark(epqstate, scanrelid, slot)) return NULL; /* Return empty slot if we haven't got a test tuple */ if (TupIsNull(slot)) return NULL; /* Check if it meets the access-method conditions */ if (!(*recheckMtd) (node, slot)) return ExecClearTuple(slot); /* would not be returned by * scan */ return slot; } } /* * Run the node-type-specific access method function to get the next tuple */ return (*accessMtd) (node); } /* ---------------------------------------------------------------- * ExecScan * * Scans the relation using the 'access method' indicated and * returns the next qualifying tuple. * The access method returns the next tuple and ExecScan() is * responsible for checking the tuple returned against the qual-clause. * * A 'recheck method' must also be provided that can check an * arbitrary tuple of the relation against any qual conditions * that are implemented internal to the access method. * * Conditions: * -- the "cursor" maintained by the AMI is positioned at the tuple * returned previously. * * Initial States: * -- the relation indicated is opened for scanning so that the * "cursor" is positioned before the first qualifying tuple. * ---------------------------------------------------------------- */ TupleTableSlot * ExecScan(ScanState *node, ExecScanAccessMtd accessMtd, /* function returning a tuple */ ExecScanRecheckMtd recheckMtd) { ExprContext *econtext; ExprState *qual; ProjectionInfo *projInfo; /* * Fetch data from node */ qual = node->ps.qual; projInfo = node->ps.ps_ProjInfo; econtext = node->ps.ps_ExprContext; /* interrupt checks are in ExecScanFetch */ /* * If we have neither a qual to check nor a projection to do, just skip * all the overhead and return the raw scan tuple. */ if (!qual && !projInfo) { ResetExprContext(econtext); return ExecScanFetch(node, accessMtd, recheckMtd); } /* * Reset per-tuple memory context to free any expression evaluation * storage allocated in the previous tuple cycle. */ ResetExprContext(econtext); /* * get a tuple from the access method. Loop until we obtain a tuple that * passes the qualification. */ for (;;) { TupleTableSlot *slot; slot = ExecScanFetch(node, accessMtd, recheckMtd); /* * if the slot returned by the accessMtd contains NULL, then it means * there is nothing more to scan so we just return an empty slot, * being careful to use the projection result slot so it has correct * tupleDesc. */ if (TupIsNull(slot)) { if (projInfo) return ExecClearTuple(projInfo->pi_state.resultslot); else return slot; } /* * place the current tuple into the expr context */ econtext->ecxt_scantuple = slot; /* * check that the current tuple satisfies the qual-clause * * check for non-null qual here to avoid a function call to ExecQual() * when the qual is null ... saves only a few cycles, but they add up * ... */ if (qual == NULL || ExecQual(qual, econtext)) { /* * Found a satisfactory scan tuple. */ if (projInfo) { /* * Form a projection tuple, store it in the result tuple slot * and return it. */ return ExecProject(projInfo); } else { /* * Here, we aren't projecting, so just return scan tuple. */ return slot; } } else InstrCountFiltered1(node, 1); /* * Tuple fails qual, so free per-tuple memory and try again. */ ResetExprContext(econtext); } } /* * ExecAssignScanProjectionInfo * Set up projection info for a scan node, if necessary. * * We can avoid a projection step if the requested tlist exactly matches * the underlying tuple type. If so, we just set ps_ProjInfo to NULL. * Note that this case occurs not only for simple "SELECT * FROM ...", but * also in most cases where there are joins or other processing nodes above * the scan node, because the planner will preferentially generate a matching * tlist. * * The scan slot's descriptor must have been set already. */ void ExecAssignScanProjectionInfo(ScanState *node) { Scan *scan = (Scan *) node->ps.plan; TupleDesc tupdesc = node->ss_ScanTupleSlot->tts_tupleDescriptor; ExecConditionalAssignProjectionInfo(&node->ps, tupdesc, scan->scanrelid); } /* * ExecAssignScanProjectionInfoWithVarno * As above, but caller can specify varno expected in Vars in the tlist. */ void ExecAssignScanProjectionInfoWithVarno(ScanState *node, int varno) { TupleDesc tupdesc = node->ss_ScanTupleSlot->tts_tupleDescriptor; ExecConditionalAssignProjectionInfo(&node->ps, tupdesc, varno); } /* * ExecScanReScan * * This must be called within the ReScan function of any plan node type * that uses ExecScan(). */ void ExecScanReScan(ScanState *node) { EState *estate = node->ps.state; /* * We must clear the scan tuple so that observers (e.g., execCurrent.c) * can tell that this plan node is not positioned on a tuple. */ ExecClearTuple(node->ss_ScanTupleSlot); /* * Rescan EvalPlanQual tuple(s) if we're inside an EvalPlanQual recheck. * But don't lose the "blocked" status of blocked target relations. */ if (estate->es_epq_active != NULL) { EPQState *epqstate = estate->es_epq_active; Index scanrelid = ((Scan *) node->ps.plan)->scanrelid; if (scanrelid > 0) epqstate->relsubs_done[scanrelid - 1] = epqstate->relsubs_blocked[scanrelid - 1]; else { Bitmapset *relids; int rtindex = -1; /* * 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_base_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); epqstate->relsubs_done[rtindex - 1] = epqstate->relsubs_blocked[rtindex - 1]; } } } }