mirror of
https://github.com/postgres/postgres.git
synced 2025-04-27 22:56:53 +03:00
414 lines
10 KiB
C
414 lines
10 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* nodeTidrangescan.c
|
|
* Routines to support TID range scans of relations
|
|
*
|
|
* Portions Copyright (c) 1996-2022, 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;
|
|
}
|