*
* tidrangescan.cpp
* 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
* Portions Copyright (c) 2021, openGauss Contributors
*
*
* IDENTIFICATION
* contrib/tidrangescan/tidrangescan.cpp
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/explain.h"
#include "executor/executor.h"
#include "executor/exec/execdebug.h"
#include "fmgr.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/paths.h"
#include "optimizer/pathnode.h"
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "optimizer/placeholder.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/subselect.h"
#include "parser/parsetree.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/spccache.h"
#include "access/tableam.h"
#include "access/htup.h"
#include "executor/node/nodeExtensible.h"
#include "commands/extension.h"
#include "tidrangescan.h"
#ifndef TIDGreaterOperator
#define TIDGreaterOperator 2800
#endif
#ifndef TIDLessEqOperator
#define TIDLessEqOperator 2801
#endif
#ifndef TIDGreaterEqOperator
#define TIDGreaterEqOperator 2802
#endif
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(tidrangescan_invoke);
* TidRangeScanState information
*
* trss_tidexprs list of TidOpExpr structs (see nodeTidrangescan.c)
* trss_mintid the lowest TID in the scan range
* trss_maxtid the highest TID in the scan range
* trss_inScan is a scan currently in progress?
* ----------------
*/
typedef struct {
ExtensiblePlanState css;
List *trss_tidexprs;
ItemPointerData trss_mintid;
ItemPointerData trss_maxtid;
bool trss_inScan;
} TidRangeScanState;
typedef enum {
TIDEXPR_UPPER_BOUND,
TIDEXPR_LOWER_BOUND
} TidExprType;
typedef struct TidOpExpr {
TidExprType exprtype;
ExprState *exprstate;
bool inclusive;
} TidOpExpr;
static void SetTidRangeScanPath(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte);
static Plan *PlanTidRangeScanPath(PlannerInfo *root, RelOptInfo *rel, struct ExtensiblePath *best_path, List *tlist,
List *clauses, List *extensible_plans);
static Node *CreateTidRangeScanState(ExtensiblePlan *extensible_plan);
static void ExecInitTidRangeScan(ExtensiblePlanState *node, EState *estate, int eflags);
static TupleTableSlot *ExecTidRangeScan(ExtensiblePlanState *node);
static void ExecEndTidRangeScan(ExtensiblePlanState *node);
static void ExplainTidRangeScan(ExtensiblePlanState *node, List *ancestors, ExplainState *es);
static void ExecReScanTidRangeScan(ExtensiblePlanState *node);
static tidrangescan_context* get_session_context();
static uint32 tidrangescan_index;
extern List* reassign_nodelist(RangeTblEntry* rte, List* ori_node_list);
#define ENABLE_TIDRANGESCAN (get_session_context()->enable_tidrangescan)
#define IsCTIDVar(node) \
((node) != NULL && IsA((node), Var) && ((Var *)(node))->varattno == SelfItemPointerAttributeNumber && \
((Var *)(node))->varlevelsup == 0)
static ExtensiblePathMethods tidrangescan_path_methods = {
"Tid Range Scan",
PlanTidRangeScanPath,
};
static ExtensiblePlanMethods tidrangescan_scan_methods = {
"Tid Range Scan",
CreateTidRangeScanState,
};
static ExtensibleExecMethods tidrangescan_exec_methods = {
"Tid Range Scan",
ExecInitTidRangeScan,
ExecTidRangeScan,
ExecEndTidRangeScan,
ExecReScanTidRangeScan,
ExplainTidRangeScan
};
void tidrangescan_invoke(void) {
ereport(DEBUG2, (errmsg("dummy function to let process load this library.")));
return;
}
List *ExecInitExprList(List *nodes, PlanState *parent)
{
List *result = NIL;
ListCell *lc;
foreach (lc, nodes) {
Expr *e = (Expr *)lfirst(lc);
result = lappend(result, ExecInitExpr(e, parent));
}
return result;
}
* Does this Var represent the CTID column of the specified baserel?
*/
static inline bool IsCTIDVarRel(Var *var, RelOptInfo *rel)
{
if (var->varattno == SelfItemPointerAttributeNumber && var->vartype == TIDOID && var->varno == rel->relid &&
var->varlevelsup == 0)
return true;
return false;
}
* Check to see if a RestrictInfo is of the form
* CTID OP pseudoconstant
* or
* pseudoconstant OP CTID
* where OP is a binary operation, the CTID Var belongs to relation "rel",
* and nothing on the other side of the clause does.
*/
static bool IsBinaryTidClause(RestrictInfo *rinfo, RelOptInfo *rel)
{
OpExpr *node;
Node *arg1, *arg2, *other;
Relids other_relids;
if (!is_opclause(rinfo->clause))
return false;
node = (OpExpr *)rinfo->clause;
if (list_length(node->args) != 2)
return false;
arg1 = (Node *)linitial(node->args);
arg2 = (Node *)lsecond(node->args);
other = NULL;
other_relids = NULL;
if (arg1 && IsA(arg1, Var) && IsCTIDVarRel((Var *)arg1, rel)) {
other = arg2;
other_relids = rinfo->right_relids;
}
if (!other && arg2 && IsA(arg2, Var) && IsCTIDVarRel((Var *)arg2, rel)) {
other = arg1;
other_relids = rinfo->left_relids;
}
if (!other)
return false;
if (bms_is_member(rel->relid, other_relids) || contain_volatile_functions(other))
return false;
return true;
}
* Check to see if a RestrictInfo is of the form
* CTID OP pseudoconstant
* or
* pseudoconstant OP CTID
* where OP is a range operator such as <, <=, >, or >=, the CTID Var belongs
* to relation "rel", and nothing on the other side of the clause does.
*/
static bool IsTidRangeClause(RestrictInfo *rinfo, RelOptInfo *rel)
{
Oid opno;
if (!IsBinaryTidClause(rinfo, rel))
return false;
opno = ((OpExpr *)rinfo->clause)->opno;
if (opno == TIDLessOperator || opno == TIDLessEqOperator || opno == TIDGreaterOperator ||
opno == TIDGreaterEqOperator)
return true;
return false;
}
* Extract a set of CTID range conditions from implicit-AND List of RestrictInfos
*
* Returns a List of CTID range qual RestrictInfos for the specified rel
* (with implicit AND semantics across the list), or NIL if there are no
* usable range conditions or if the rel's table AM does not support TID range
* scans.
*/
static List *TidRangeQualFromRestrictInfoList(List *rlist, RelOptInfo *rel)
{
List *rlst = NIL;
ListCell *l;
foreach (l, rlist) {
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
if (IsTidRangeClause(rinfo, rel))
rlst = lappend(rlst, rinfo);
}
return rlst;
}
* cost_tidrangescan
* Determines and sets the costs of scanning a relation using a range of
* TIDs for 'path'
*
* 'baserel' is the relation to be scanned
* 'tidrangequals' is the list of TID-checkable range quals
* 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
*/
void cost_tidrangescan(Path *path, PlannerInfo *root, RelOptInfo *baserel, List *tidrangequals,
ParamPathInfo *param_info)
{
Selectivity selectivity;
double pages;
Cost startup_cost = 0;
Cost run_cost = 0;
QualCost qpqual_cost;
Cost cpu_per_tuple;
QualCost tid_qual_cost;
double ntuples;
double nseqpages;
double spc_random_page_cost;
double spc_seq_page_cost;
Assert(baserel->relid > 0);
Assert(baserel->rtekind == RTE_RELATION);
if (param_info)
path->rows = param_info->ppi_rows;
else
path->rows = baserel->rows;
selectivity = clauselist_selectivity(root, tidrangequals, baserel->relid, JOIN_INNER, NULL);
pages = ceil(selectivity * baserel->pages);
if (pages <= 0.0)
pages = 1.0;
* The first page in a range requires a random seek, but each subsequent
* page is just a normal sequential page read. NOTE: it's desirable for
* TID Range Scans to cost more than the equivalent Sequential Scans,
* because Seq Scans have some performance advantages such as scan
* synchronization and parallelizability, and we'd prefer one of them to
* be picked unless a TID Range Scan really is better.
*/
ntuples = selectivity * baserel->tuples;
nseqpages = pages - 1.0;
if (!ENABLE_TIDRANGESCAN)
startup_cost += g_instance.cost_cxt.disable_cost;
* The TID qual expressions will be computed once, any other baserestrict
* quals once per retrieved tuple.
*/
cost_qual_eval(&tid_qual_cost, tidrangequals, root);
get_tablespace_page_costs(baserel->reltablespace, &spc_random_page_cost, &spc_seq_page_cost);
run_cost += spc_random_page_cost + spc_seq_page_cost * nseqpages;
if (param_info != NULL) {
cost_qual_eval(&qpqual_cost, param_info->ppi_clauses, root);
qpqual_cost.startup += baserel->baserestrictcost.startup;
qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple;
} else {
qpqual_cost = baserel->baserestrictcost;
}
* XXX currently we assume TID quals are a subset of qpquals at this
* point; they will be removed (if possible) when we create the plan, so
* we subtract their cost from the total qpqual cost. (If the TID quals
* can't be removed, this is a mistake and we're going to underestimate
* the CPU cost a bit.)
*/
startup_cost += qpqual_cost.startup + tid_qual_cost.per_tuple;
cpu_per_tuple = u_sess->attr.attr_sql.cpu_tuple_cost + qpqual_cost.per_tuple - tid_qual_cost.per_tuple;
run_cost += cpu_per_tuple * ntuples;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
* SetTidRangeScanPath - entrypoint of the series of extensible-scan execution.
* It adds ExtensiblePath if referenced relation has inequality expressions on
* the ctid system column.
*/
static void SetTidRangeScanPath(PlannerInfo *root, RelOptInfo *baserel, Index rtindex, RangeTblEntry *rte)
{
List *tidrangequals = NIL;
if (baserel->rtekind != RTE_RELATION || rte->relkind == RELKIND_FOREIGN_TABLE || rte->tablesample != NULL ||
baserel->isPartitionedTable || rte->orientation != REL_ROW_ORIENTED)
return;
* If there are range quals in the baserestrict list, generate a
* TidRangePath.
*/
tidrangequals = TidRangeQualFromRestrictInfoList(baserel->baserestrictinfo, baserel);
if (tidrangequals != NIL) {
ExtensiblePath *cpath = makeNode(ExtensiblePath);
Relids required_outer;
* This path uses no join clauses, but it could still have required
* parameterization due to LATERAL refs in its tlist.
*/
required_outer = baserel->lateral_relids;
cpath->path.pathtype = T_ExtensiblePlan;
cpath->path.parent = baserel;
cpath->path.param_info = get_baserel_parampathinfo(root, baserel, required_outer);
cpath->path.pathkeys = NIL;
cpath->path.pathtarget = baserel->reltarget;
cpath->flags = EXTENSIBLEPATH_SUPPORT_BACKWARD_SCAN | EXTENSIBLEPATH_SUPPORT_PROJECTION;
cpath->extensible_private = tidrangequals;
cpath->methods = &tidrangescan_path_methods;
cost_tidrangescan(&cpath->path, root, baserel, tidrangequals, cpath->path.param_info);
add_path(root, baserel, &cpath->path);
}
}
static void add_distribute_info(PlannerInfo* root, Plan* scanPlan, Index relIndex, Path* bestPath, List* scanClauses)
{
ExecNodes* execNodes = NULL;
if (unlikely(root->simple_rte_array == NULL)) {
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("Range table should not be null")));
}
RangeTblEntry* rte = root->simple_rte_array[relIndex];
if (is_sys_table(rte->relid) || rte->relkind == RELKIND_SEQUENCE) {
execNodes = ng_convert_to_exec_nodes(&bestPath->distribution, bestPath->locator_type, RELATION_ACCESS_READ);
scanPlan->exec_type = EXEC_ON_COORDS;
} else if (IsToastNamespace(get_rel_namespace(rte->relid))) {
execNodes = ng_convert_to_exec_nodes(&bestPath->distribution, bestPath->locator_type, RELATION_ACCESS_READ);
scanPlan->exec_type = EXEC_ON_DATANODES;
} else {
if (GetLocatorType(rte->relid) == LOCATOR_TYPE_REPLICATED) {
Assert(bestPath->locator_type == LOCATOR_TYPE_REPLICATED);
execNodes = ng_convert_to_exec_nodes(&bestPath->distribution, bestPath->locator_type, RELATION_ACCESS_READ);
} else {
List* quals = scanClauses;
execNodes = GetRelationNodesByQuals(
(void*)root->parse, rte->relid, relIndex, (Node*)quals, RELATION_ACCESS_READ, NULL, false);
if (execNodes == NULL) {
elog(DEBUG1, "[add_distribute_info] execNodes is NULL. Oid [%u]", rte->relid);
Assert(rte->relid < FirstNormalObjectId || IS_PGXC_DATANODE);
execNodes = makeNode(ExecNodes);
Distribution* distribution = ng_get_installation_group_distribution();
ng_set_distribution(&execNodes->distribution, distribution);
execNodes->nodeList = ng_convert_to_nodeid_list(execNodes->distribution.bms_data_nodeids);
}
if (!IsLocatorDistributedBySlice(execNodes->baselocatortype)) {
execNodes->baselocatortype = LOCATOR_TYPE_HASH;
}
}
scanPlan->exec_type = EXEC_ON_DATANODES;
}
scanPlan->exec_nodes = execNodes;
scanPlan->distributed_keys = bestPath->parent->distribute_keys;
}
* PlanTidRangeScanPath - A method of ExtensiblePath; that populate a extensible
* object being delivered from ExtensiblePlan type, according to the supplied
* EXtensiblePath object.
*/
static Plan *PlanTidRangeScanPath(PlannerInfo *root, RelOptInfo *rel, struct ExtensiblePath *best_path, List *tlist,
List *clauses, List *extensible_plans)
{
ExtensiblePlan *node = makeNode(ExtensiblePlan);
Index scan_relid = best_path->path.parent->relid;
List *tidrangequals = best_path->extensible_private;
Plan *plan = &node->scan.plan;
Assert(scan_relid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
* The qpqual list must contain all restrictions not enforced by the
* tidrangequals list. tidrangequals has AND semantics, so we can simply
* remove any qual that appears in it.
*/
{
List *qpqual = NIL;
ListCell *l;
foreach (l, clauses) {
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
if (rinfo->pseudoconstant)
continue;
if (list_member_ptr(tidrangequals, rinfo))
continue;
qpqual = lappend(qpqual, rinfo);
}
clauses = qpqual;
}
tidrangequals = extract_actual_clauses(tidrangequals, false);
clauses = extract_actual_clauses(clauses, false);
plan->targetlist = tlist;
plan->qual = clauses;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scan_relid;
node->extensible_exprs = tidrangequals;
#ifdef STREAMPLAN
add_distribute_info(root, plan, scan_relid, &best_path->path, clauses);
#endif
node->flags = best_path->flags;
node->scan.plan.startup_cost = best_path->path.startup_cost;
node->scan.plan.total_cost = best_path->path.total_cost;
node->scan.plan.plan_rows = best_path->path.rows;
node->scan.plan.plan_width = best_path->path.pathtarget->width;
node->methods = &tidrangescan_scan_methods;
return plan;
}
* CreateTidRangeScanState - A method of ExtensiblePlan; that populate a extensible
* object being delivered from ExtensiblePlanState type, according to the
* supplied ExtensiblePath object.
*/
Node *CreateTidRangeScanState(ExtensiblePlan *extensible_plan)
{
TidRangeScanState *tidrangestate;
* create state structure
*/
tidrangestate = (TidRangeScanState *)palloc0(sizeof(TidRangeScanState));
NodeSetTag(tidrangestate, T_ExtensiblePlanState);
tidrangestate->css.methods = &tidrangescan_exec_methods;
* mark scan as not in progress, and TID range as not computed yet
*/
tidrangestate->trss_inScan = false;
return (Node *)&tidrangestate->css;
}
* 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->css.ss.ps);
else if (IsCTIDVar(arg2)) {
exprstate = ExecInitExpr((Expr *)arg1, &tidstate->css.ss.ps);
invert = true;
} else
elog(ERROR, "could not identify CTID variable");
tidopexpr = (TidOpExpr *)palloc(sizeof(TidOpExpr));
tidopexpr->inclusive = false;
switch (expr->opno) {
case TIDLessEqOperator:
tidopexpr->inclusive = true;
case TIDLessOperator:
tidopexpr->exprtype = invert ? TIDEXPR_LOWER_BOUND : TIDEXPR_UPPER_BOUND;
break;
case TIDGreaterEqOperator:
tidopexpr->inclusive = true;
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)
{
ExtensiblePlan *node = (ExtensiblePlan *)tidrangestate->css.ss.ps.plan;
List *tidexprs = NIL;
ListCell *l;
foreach (l, node->extensible_exprs) {
OpExpr *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;
}
* 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.
* ----------------------------------------------------------------
*/
static void ExecInitTidRangeScan(ExtensiblePlanState *node, EState *estate, int eflags)
{
TidRangeScanState *ctss = (TidRangeScanState *)node;
ctss->css.ss.ss_currentScanDesc = NULL;
TidExprListCreate(ctss);
}
* ItemPointerInc
* Increment 'pointer' by 1 only paying attention to the ItemPointer's
* type's range limits and not MaxOffsetNumber and FirstOffsetNumber.
* This may result in 'pointer' becoming !OffsetNumberIsValid.
*
* If the pointer is already the maximum possible values permitted by the
* range of the ItemPointer's types, then do nothing.
*/
void ItemPointerInc(ItemPointer pointer)
{
BlockNumber blk = ItemPointerGetBlockNumberNoCheck(pointer);
OffsetNumber off = ItemPointerGetOffsetNumberNoCheck(pointer);
if (off == PG_UINT16_MAX) {
if (blk != InvalidBlockNumber) {
off = 0;
blk++;
}
} else
off++;
ItemPointerSet(pointer, blk, off);
}
* ItemPointerDec
* Decrement 'pointer' by 1 only paying attention to the ItemPointer's
* type's range limits and not MaxOffsetNumber and FirstOffsetNumber.
* This may result in 'pointer' becoming !OffsetNumberIsValid.
*
* If the pointer is already the minimum possible values permitted by the
* range of the ItemPointer's types, then do nothing. This does rely on
* FirstOffsetNumber being 1 rather than 0.
*/
void ItemPointerDec(ItemPointer pointer)
{
BlockNumber blk = ItemPointerGetBlockNumberNoCheck(pointer);
OffsetNumber off = ItemPointerGetOffsetNumberNoCheck(pointer);
if (off == 0) {
if (blk != 0) {
off = PG_UINT16_MAX;
blk--;
}
} else
off--;
ItemPointerSet(pointer, blk, off);
}
* 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->css.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;
itemptr =
(ItemPointer)DatumGetPointer(ExecEvalExprSwitchContext(tidopexpr->exprstate, econtext, &isNull, NULL));
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);
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);
if (ItemPointerCompare(&ub, &upperBound) < 0)
ItemPointerCopy(&ub, &upperBound);
}
}
ItemPointerCopy(&lowerBound, &node->trss_mintid);
ItemPointerCopy(&upperBound, &node->trss_maxtid);
return true;
}
bool heap_getnextslot_tidrange(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot,
ItemPointer mintid, ItemPointer maxtid)
{
HeapTuple tuple;
while((tuple = heap_getnext(sscan, direction)) != NULL) {
if (ItemPointerCompare(&tuple->t_self, mintid) < 0)
{
ExecClearTuple(slot);
* When scanning backwards, the TIDs will be in descending order.
* Future tuples in this direction will be lower still, so we can
* just return false to indicate there will be no more tuples.
*/
if (ScanDirectionIsBackward(direction))
return false;
continue;
}
* Likewise for the final page, we must filter out TIDs greater than
* maxtid.
*/
if (ItemPointerCompare(&tuple->t_self, maxtid) > 0)
{
ExecClearTuple(slot);
* When scanning forward, the TIDs will be in ascending order.
* Future tuples in this direction will be higher still, so we can
* just return false to indicate there will be no more tuples.
*/
if (ScanDirectionIsForward(direction))
return false;
continue;
}
break;
}
if (tuple) {
ExecStoreTuple(tuple, slot, sscan->rs_cbuf, false);
return true;
}
ExecClearTuple(slot);
return false;
}
* heap_setscanlimits - restrict range of a heapscan
*
* startBlk is the page to start at
* numBlks is number of pages to scan (InvalidBlockNumber means "all")
*/
void heap_setscanlimits(TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
{
Assert(!sscan->rs_inited);
Assert(!(sscan->rs_flags & SO_ALLOW_SYNC));
Assert(startBlk == 0 || startBlk < sscan->rs_nblocks);
sscan->rs_startblock = startBlk;
}
void heap_set_tidrange(TableScanDesc sscan, ItemPointer mintid, ItemPointer maxtid)
{
BlockNumber startBlk;
BlockNumber numBlks;
ItemPointerData highestItem;
ItemPointerData lowestItem;
* For relations without any pages, we can simply leave the TID range
* unset. There will be no tuples to scan, therefore no tuples outside
* the given TID range.
*/
if (sscan->rs_nblocks == 0)
return;
* Set up some ItemPointers which point to the first and last possible
* tuples in the heap.
*/
ItemPointerSet(&highestItem, sscan->rs_nblocks - 1, MaxOffsetNumber);
ItemPointerSet(&lowestItem, 0, FirstOffsetNumber);
* If the given maximum TID is below the highest possible TID in the
* relation, then restrict the range to that, otherwise we scan to the end
* of the relation.
*/
if (ItemPointerCompare(maxtid, &highestItem) < 0)
ItemPointerCopy(maxtid, &highestItem);
* If the given minimum TID is above the lowest possible TID in the
* relation, then restrict the range to only scan for TIDs above that.
*/
if (ItemPointerCompare(mintid, &lowestItem) > 0)
ItemPointerCopy(mintid, &lowestItem);
* Check for an empty range and protect from would be negative results
* from the numBlks calculation below.
*/
if (ItemPointerCompare(&highestItem, &lowestItem) < 0)
{
heap_setscanlimits(sscan, 0, 0);
return;
}
* Calculate the first block and the number of blocks we must scan. We
* could be more aggressive here and perform some more validation to try
* and further narrow the scope of blocks to scan by checking if the
* lowerItem has an offset above MaxOffsetNumber. In this case, we could
* advance startBlk by one. Likewise, if highestItem has an offset of 0
* we could scan one fewer blocks. However, such an optimization does not
* seem worth troubling over, currently.
*/
startBlk = ItemPointerGetBlockNumberNoCheck(&lowestItem);
numBlks = ItemPointerGetBlockNumberNoCheck(&highestItem) -
ItemPointerGetBlockNumberNoCheck(&lowestItem) + 1;
heap_setscanlimits(sscan, startBlk, numBlks);
}
* ExecReScanTidRangeScan(node)
* ----------------------------------------------------------------
*/
static void ExecReScanTidRangeScan(ExtensiblePlanState *node)
{
((TidRangeScanState *)node)->trss_inScan = false;
* We must wait until TidRangeNext before calling table_rescan_tidrange.
*/
ExecScanReScan(&node->ss);
}
#define SO_TYPE_TIDRANGESCAN 1 << 9
* table_beginscan_tidrange is the entry point for setting up a TableScanDesc
* for a TID range scan.
*/
static inline TableScanDesc table_beginscan_tidrange(Relation rel, Snapshot snapshot, ItemPointer mintid,
ItemPointer maxtid)
{
TableScanDesc sscan;
uint32 flags = SO_TYPE_TIDRANGESCAN | SO_ALLOW_PAGEMODE;
sscan = heap_beginscan(rel, snapshot, 0, NULL);
sscan->rs_flags = flags;
heap_set_tidrange(sscan, mintid, maxtid);
return sscan;
}
* table_rescan_tidrange resets the scan position and sets the minimum and
* maximum TID range to scan for a TableScanDesc created by
* table_beginscan_tidrange.
*/
static inline void table_rescan_tidrange(TableScanDesc sscan, ItemPointer mintid, ItemPointer maxtid)
{
Assert((sscan->rs_flags & SO_TYPE_TIDRANGESCAN) != 0);
heap_rescan(sscan, NULL);
heap_set_tidrange(sscan, mintid, maxtid);
}
* TidRangeNext
*
* Retrieve a tuple from the TidRangeScan node's currentRelation
* using the TIDs in the TidRangeScanState information.
*
* ----------------------------------------------------------------
*/
static TupleTableSlot *TidRangeNext(ExtensiblePlanState *node)
{
TableScanDesc scandesc;
EState *estate;
ScanDirection direction;
TupleTableSlot *slot;
TidRangeScanState *trss;
* 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;
trss = (TidRangeScanState *)node;
if (!trss->trss_inScan) {
if (!TidRangeEval(trss))
return NULL;
if (scandesc == NULL) {
scandesc = table_beginscan_tidrange(node->ss.ss_currentRelation, estate->es_snapshot, &trss->trss_mintid,
&trss->trss_maxtid);
node->ss.ss_currentScanDesc = scandesc;
} else {
table_rescan_tidrange(scandesc, &trss->trss_mintid, &trss->trss_maxtid);
}
trss->trss_inScan = true;
}
if (!heap_getnextslot_tidrange(scandesc, direction, slot, &trss->trss_mintid, &trss->trss_maxtid)) {
trss->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(ExtensiblePlanState *pstate)
{
return ExecScan(&pstate->ss, (ExecScanAccessMtd)TidRangeNext, (ExecScanRecheckMtd)TidRangeRecheck);
}
* ExecEndTidRangeScan
*
* Releases any storage allocated through C routines.
* Returns nothing.
* ----------------------------------------------------------------
*/
static void ExecEndTidRangeScan(ExtensiblePlanState *node)
{
TableScanDesc scan = node->ss.ss_currentScanDesc;
if (scan != NULL)
heap_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);
}
* ExplainTidRangeScan - A method of ExtensiblePlanState; that shows extra info
* on EXPLAIN command.
*/
static void ExplainTidRangeScan(ExtensiblePlanState *node, List *ancestors, ExplainState *es)
{
TidRangeScanState *ctss = (TidRangeScanState *) node;
ExtensiblePlan *cscan = (ExtensiblePlan *) ctss->css.ss.ps.plan;
if (cscan->extensible_exprs)
{
bool useprefix = es->verbose;
Node *qual;
List *context;
char *exprstr;
* The tidrangequals list has AND semantics, so be sure to
* show it as an AND condition.
*/
qual = (Node*)make_ands_explicit(cscan->extensible_exprs);
context = deparse_context_for_planstate((Node*)ctss, ancestors, es->rtable);
exprstr = deparse_expression(qual, context, useprefix, false);
ExplainPropertyText("TID Cond", exprstr, es);
}
}
static tidrangescan_context* get_session_context()
{
if (u_sess->attr.attr_common.extension_session_vars_array[tidrangescan_index] == NULL) {
init_session_vars();
}
return (tidrangescan_context*)u_sess->attr.attr_common.extension_session_vars_array[tidrangescan_index];
}
void set_extension_index(uint32 index)
{
tidrangescan_index = index;
}
void init_session_vars(void)
{
RepallocSessionVarsArrayIfNecessary();
tidrangescan_context *cxt = (tidrangescan_context*)MemoryContextAlloc(u_sess->self_mem_cxt, sizeof(tidrangescan_context));
u_sess->attr.attr_common.extension_session_vars_array[tidrangescan_index] = cxt;
cxt->enable_tidrangescan = true;
DefineCustomBoolVariable("enable_tidrangescan",
"Enables the planner's use of tidrange-scan plans.",
NULL,
&ENABLE_TIDRANGESCAN,
true,
PGC_USERSET,
0,
NULL, NULL, NULL);
}
* Entrypoint of this extension
*/
void _PG_init(void)
{
ereport(LOG, (errmsg("init tidrangescan.")));
set_rel_pathlist_hook = SetTidRangeScanPath;
}
void _PG_fini(void)
{
set_rel_pathlist_hook = NULL;
}