| /*------------------------------------------------------------------------- |
| * |
| * nodeTidrangescan.c |
| * Routines to support TID range scans of relations |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/executor/nodeTidrangescan.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/relscan.h" |
| #include "access/sysattr.h" |
| #include "access/tableam.h" |
| #include "catalog/pg_operator.h" |
| #include "executor/execdebug.h" |
| #include "executor/nodeTidrangescan.h" |
| #include "nodes/nodeFuncs.h" |
| #include "storage/bufmgr.h" |
| #include "utils/rel.h" |
| |
| |
| #define IsCTIDVar(node) \ |
| ((node) != NULL && \ |
| IsA((node), Var) && \ |
| ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \ |
| ((Var *) (node))->varlevelsup == 0) |
| |
| typedef enum |
| { |
| TIDEXPR_UPPER_BOUND, |
| TIDEXPR_LOWER_BOUND |
| } TidExprType; |
| |
| /* Upper or lower range bound for scan */ |
| typedef struct TidOpExpr |
| { |
| TidExprType exprtype; /* type of op; lower or upper */ |
| ExprState *exprstate; /* ExprState for a TID-yielding subexpr */ |
| bool inclusive; /* whether op is inclusive */ |
| } TidOpExpr; |
| |
| /* |
| * For the given 'expr', build and return an appropriate TidOpExpr taking into |
| * account the expr's operator and operand order. |
| */ |
| static TidOpExpr * |
| MakeTidOpExpr(OpExpr *expr, TidRangeScanState *tidstate) |
| { |
| Node *arg1 = get_leftop((Expr *) expr); |
| Node *arg2 = get_rightop((Expr *) expr); |
| ExprState *exprstate = NULL; |
| bool invert = false; |
| TidOpExpr *tidopexpr; |
| |
| if (IsCTIDVar(arg1)) |
| exprstate = ExecInitExpr((Expr *) arg2, &tidstate->ss.ps); |
| else if (IsCTIDVar(arg2)) |
| { |
| exprstate = ExecInitExpr((Expr *) arg1, &tidstate->ss.ps); |
| invert = true; |
| } |
| else |
| elog(ERROR, "could not identify CTID variable"); |
| |
| tidopexpr = (TidOpExpr *) palloc(sizeof(TidOpExpr)); |
| tidopexpr->inclusive = false; /* for now */ |
| |
| switch (expr->opno) |
| { |
| case TIDLessEqOperator: |
| tidopexpr->inclusive = true; |
| /* fall through */ |
| case TIDLessOperator: |
| tidopexpr->exprtype = invert ? TIDEXPR_LOWER_BOUND : TIDEXPR_UPPER_BOUND; |
| break; |
| case TIDGreaterEqOperator: |
| tidopexpr->inclusive = true; |
| /* fall through */ |
| case TIDGreaterOperator: |
| tidopexpr->exprtype = invert ? TIDEXPR_UPPER_BOUND : TIDEXPR_LOWER_BOUND; |
| break; |
| default: |
| elog(ERROR, "could not identify CTID operator"); |
| } |
| |
| tidopexpr->exprstate = exprstate; |
| |
| return tidopexpr; |
| } |
| |
| /* |
| * Extract the qual subexpressions that yield TIDs to search for, |
| * and compile them into ExprStates if they're ordinary expressions. |
| */ |
| static void |
| TidExprListCreate(TidRangeScanState *tidrangestate) |
| { |
| TidRangeScan *node = (TidRangeScan *) tidrangestate->ss.ps.plan; |
| List *tidexprs = NIL; |
| ListCell *l; |
| |
| foreach(l, node->tidrangequals) |
| { |
| OpExpr *opexpr = lfirst(l); |
| TidOpExpr *tidopexpr; |
| |
| if (!IsA(opexpr, OpExpr)) |
| elog(ERROR, "could not identify CTID expression"); |
| |
| tidopexpr = MakeTidOpExpr(opexpr, tidrangestate); |
| tidexprs = lappend(tidexprs, tidopexpr); |
| } |
| |
| tidrangestate->trss_tidexprs = tidexprs; |
| } |
| |
| /* ---------------------------------------------------------------- |
| * TidRangeEval |
| * |
| * Compute and set node's block and offset range to scan by evaluating |
| * the trss_tidexprs. Returns false if we detect the range cannot |
| * contain any tuples. Returns true if it's possible for the range to |
| * contain tuples. |
| * ---------------------------------------------------------------- |
| */ |
| static bool |
| TidRangeEval(TidRangeScanState *node) |
| { |
| ExprContext *econtext = node->ss.ps.ps_ExprContext; |
| ItemPointerData lowerBound; |
| ItemPointerData upperBound; |
| ListCell *l; |
| |
| /* |
| * Set the upper and lower bounds to the absolute limits of the range of |
| * the ItemPointer type. Below we'll try to narrow this range on either |
| * side by looking at the TidOpExprs. |
| */ |
| ItemPointerSet(&lowerBound, 0, 0); |
| ItemPointerSet(&upperBound, InvalidBlockNumber, PG_UINT16_MAX); |
| |
| foreach(l, node->trss_tidexprs) |
| { |
| TidOpExpr *tidopexpr = (TidOpExpr *) lfirst(l); |
| ItemPointer itemptr; |
| bool isNull; |
| |
| /* Evaluate this bound. */ |
| itemptr = (ItemPointer) |
| DatumGetPointer(ExecEvalExprSwitchContext(tidopexpr->exprstate, |
| econtext, |
| &isNull)); |
| |
| /* If the bound is NULL, *nothing* matches the qual. */ |
| if (isNull) |
| return false; |
| |
| if (tidopexpr->exprtype == TIDEXPR_LOWER_BOUND) |
| { |
| ItemPointerData lb; |
| |
| ItemPointerCopy(itemptr, &lb); |
| |
| /* |
| * Normalize non-inclusive ranges to become inclusive. The |
| * resulting ItemPointer here may not be a valid item pointer. |
| */ |
| if (!tidopexpr->inclusive) |
| ItemPointerInc(&lb); |
| |
| /* Check if we can narrow the range using this qual */ |
| if (ItemPointerCompare(&lb, &lowerBound) > 0) |
| ItemPointerCopy(&lb, &lowerBound); |
| } |
| |
| else if (tidopexpr->exprtype == TIDEXPR_UPPER_BOUND) |
| { |
| ItemPointerData ub; |
| |
| ItemPointerCopy(itemptr, &ub); |
| |
| /* |
| * Normalize non-inclusive ranges to become inclusive. The |
| * resulting ItemPointer here may not be a valid item pointer. |
| */ |
| if (!tidopexpr->inclusive) |
| ItemPointerDec(&ub); |
| |
| /* Check if we can narrow the range using this qual */ |
| if (ItemPointerCompare(&ub, &upperBound) < 0) |
| ItemPointerCopy(&ub, &upperBound); |
| } |
| } |
| |
| ItemPointerCopy(&lowerBound, &node->trss_mintid); |
| ItemPointerCopy(&upperBound, &node->trss_maxtid); |
| |
| return true; |
| } |
| |
| /* ---------------------------------------------------------------- |
| * TidRangeNext |
| * |
| * Retrieve a tuple from the TidRangeScan node's currentRelation |
| * using the TIDs in the TidRangeScanState information. |
| * |
| * ---------------------------------------------------------------- |
| */ |
| static TupleTableSlot * |
| TidRangeNext(TidRangeScanState *node) |
| { |
| TableScanDesc scandesc; |
| EState *estate; |
| ScanDirection direction; |
| TupleTableSlot *slot; |
| |
| /* |
| * extract necessary information from TID scan node |
| */ |
| scandesc = node->ss.ss_currentScanDesc; |
| estate = node->ss.ps.state; |
| slot = node->ss.ss_ScanTupleSlot; |
| direction = estate->es_direction; |
| |
| if (!node->trss_inScan) |
| { |
| /* First time through, compute TID range to scan */ |
| if (!TidRangeEval(node)) |
| return NULL; |
| |
| if (scandesc == NULL) |
| { |
| scandesc = table_beginscan_tidrange(node->ss.ss_currentRelation, |
| estate->es_snapshot, |
| &node->trss_mintid, |
| &node->trss_maxtid); |
| node->ss.ss_currentScanDesc = scandesc; |
| } |
| else |
| { |
| /* rescan with the updated TID range */ |
| table_rescan_tidrange(scandesc, &node->trss_mintid, |
| &node->trss_maxtid); |
| } |
| |
| node->trss_inScan = true; |
| } |
| |
| /* Fetch the next tuple. */ |
| if (!table_scan_getnextslot_tidrange(scandesc, direction, slot)) |
| { |
| node->trss_inScan = false; |
| ExecClearTuple(slot); |
| } |
| |
| return slot; |
| } |
| |
| /* |
| * TidRangeRecheck -- access method routine to recheck a tuple in EvalPlanQual |
| */ |
| static bool |
| TidRangeRecheck(TidRangeScanState *node, TupleTableSlot *slot) |
| { |
| return true; |
| } |
| |
| /* ---------------------------------------------------------------- |
| * ExecTidRangeScan(node) |
| * |
| * Scans the relation using tids and returns the next qualifying tuple. |
| * We call the ExecScan() routine and pass it the appropriate |
| * access method functions. |
| * |
| * Conditions: |
| * -- the "cursor" maintained by the AMI is positioned at the tuple |
| * returned previously. |
| * |
| * Initial States: |
| * -- the relation indicated is opened for TID range scanning. |
| * ---------------------------------------------------------------- |
| */ |
| static TupleTableSlot * |
| ExecTidRangeScan(PlanState *pstate) |
| { |
| TidRangeScanState *node = castNode(TidRangeScanState, pstate); |
| |
| return ExecScan(&node->ss, |
| (ExecScanAccessMtd) TidRangeNext, |
| (ExecScanRecheckMtd) TidRangeRecheck); |
| } |
| |
| /* ---------------------------------------------------------------- |
| * ExecReScanTidRangeScan(node) |
| * ---------------------------------------------------------------- |
| */ |
| void |
| ExecReScanTidRangeScan(TidRangeScanState *node) |
| { |
| /* mark scan as not in progress, and tid range list as not computed yet */ |
| node->trss_inScan = false; |
| |
| /* |
| * We must wait until TidRangeNext before calling table_rescan_tidrange. |
| */ |
| ExecScanReScan(&node->ss); |
| } |
| |
| /* ---------------------------------------------------------------- |
| * ExecEndTidRangeScan |
| * |
| * Releases any storage allocated through C routines. |
| * Returns nothing. |
| * ---------------------------------------------------------------- |
| */ |
| void |
| ExecEndTidRangeScan(TidRangeScanState *node) |
| { |
| TableScanDesc scan = node->ss.ss_currentScanDesc; |
| |
| if (scan != NULL) |
| table_endscan(scan); |
| |
| /* |
| * Free the exprcontext |
| */ |
| ExecFreeExprContext(&node->ss.ps); |
| |
| /* |
| * clear out tuple table slots |
| */ |
| if (node->ss.ps.ps_ResultTupleSlot) |
| ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); |
| ExecClearTuple(node->ss.ss_ScanTupleSlot); |
| } |
| |
| /* ---------------------------------------------------------------- |
| * ExecInitTidRangeScan |
| * |
| * Initializes the tid range scan's state information, creates |
| * scan keys, and opens the scan relation. |
| * |
| * Parameters: |
| * node: TidRangeScan node produced by the planner. |
| * estate: the execution state initialized in InitPlan. |
| * ---------------------------------------------------------------- |
| */ |
| TidRangeScanState * |
| ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags) |
| { |
| TidRangeScanState *tidrangestate; |
| Relation currentRelation; |
| |
| /* |
| * create state structure |
| */ |
| tidrangestate = makeNode(TidRangeScanState); |
| tidrangestate->ss.ps.plan = (Plan *) node; |
| tidrangestate->ss.ps.state = estate; |
| tidrangestate->ss.ps.ExecProcNode = ExecTidRangeScan; |
| |
| /* |
| * Miscellaneous initialization |
| * |
| * create expression context for node |
| */ |
| ExecAssignExprContext(estate, &tidrangestate->ss.ps); |
| |
| /* |
| * mark scan as not in progress, and TID range as not computed yet |
| */ |
| tidrangestate->trss_inScan = false; |
| |
| /* |
| * open the scan relation |
| */ |
| currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags); |
| |
| tidrangestate->ss.ss_currentRelation = currentRelation; |
| tidrangestate->ss.ss_currentScanDesc = NULL; /* no table scan here */ |
| |
| /* |
| * get the scan type from the relation descriptor. |
| */ |
| ExecInitScanTupleSlot(estate, &tidrangestate->ss, |
| RelationGetDescr(currentRelation), |
| table_slot_callbacks(currentRelation)); |
| |
| /* |
| * Initialize result type and projection. |
| */ |
| ExecInitResultTypeTL(&tidrangestate->ss.ps); |
| ExecAssignScanProjectionInfo(&tidrangestate->ss); |
| |
| /* |
| * initialize child expressions |
| */ |
| tidrangestate->ss.ps.qual = |
| ExecInitQual(node->scan.plan.qual, (PlanState *) tidrangestate); |
| |
| TidExprListCreate(tidrangestate); |
| |
| /* |
| * all done. |
| */ |
| return tidrangestate; |
| } |