diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 72395a5..2e50d83 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -575,11 +575,20 @@ heapgettup(HeapScanDesc scan, * forward scanners. */ scan->rs_syncscan = false; - /* start from last page of the scan */ - if (scan->rs_startblock > 0) - page = scan->rs_startblock - 1; + + /* Start from last page of the scan. */ + if (scan->rs_numblocks == InvalidBlockNumber) + { + if (scan->rs_startblock > 0) + page = scan->rs_startblock - 1; + else + page = scan->rs_nblocks - 1; + } else - page = scan->rs_nblocks - 1; + { + page = scan->rs_startblock + scan->rs_numblocks - 1; + } + heapgetpage(scan, page); } else @@ -876,11 +885,18 @@ heapgettup_pagemode(HeapScanDesc scan, * forward scanners. */ scan->rs_syncscan = false; + /* start from last page of the scan */ - if (scan->rs_startblock > 0) - page = scan->rs_startblock - 1; - else - page = scan->rs_nblocks - 1; + if (scan->rs_numblocks == InvalidBlockNumber) { + if (scan->rs_startblock > 0) + page = scan->rs_startblock - 1; + else + page = scan->rs_nblocks - 1; + } + else { + page = scan->rs_startblock + scan->rs_numblocks - 1; + } + heapgetpage(scan, page); } else diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index d9deb72..b9472c0 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -859,6 +859,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) case T_IndexOnlyScan: case T_BitmapHeapScan: case T_TidScan: + case T_TidRangeScan: case T_SubqueryScan: case T_FunctionScan: case T_TableFuncScan: @@ -1005,6 +1006,9 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_TidScan: pname = sname = "Tid Scan"; break; + case T_TidRangeScan: + pname = sname = "Tid Range Scan"; + break; case T_SubqueryScan: pname = sname = "Subquery Scan"; break; @@ -1190,22 +1194,25 @@ ExplainNode(PlanState *planstate, List *ancestors, ExplainPropertyBool("Parallel Aware", plan->parallel_aware, es); } - switch (nodeTag(plan)) - { - case T_SeqScan: - case T_SampleScan: - case T_BitmapHeapScan: - case T_SubqueryScan: - case T_FunctionScan: - case T_TableFuncScan: - case T_ValuesScan: - case T_CteScan: - case T_WorkTableScan: - ExplainScanTarget((Scan *) plan, es); - break; - case T_TidScan: - show_scan_direction(es, ((TidScan *) plan)->direction); - ExplainScanTarget((Scan *) plan, es); + switch (nodeTag(plan)) { + case T_SeqScan: + case T_SampleScan: + case T_BitmapHeapScan: + case T_SubqueryScan: + case T_FunctionScan: + case T_TableFuncScan: + case T_ValuesScan: + case T_CteScan: + case T_WorkTableScan: + ExplainScanTarget((Scan *) plan, es); + break; + case T_TidScan: + case T_TidRangeScan: + { + ScanDirection dir = IsA(plan, TidScan) ? ((TidScan *) plan)->direction : ((TidRangeScan *) plan)->direction; + show_scan_direction(es, dir); + ExplainScanTarget((Scan *) plan, es); + } break; case T_ForeignScan: case T_CustomScan: @@ -1601,6 +1608,16 @@ ExplainNode(PlanState *planstate, List *ancestors, planstate, es); } break; + case T_TidRangeScan: + { + List *tidquals = ((TidRangeScan *) plan)->tidquals; + show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es); + show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); + if (plan->qual) + show_instrumentation_count("Rows Removed by Filter", 1, + planstate, es); + } + break; case T_ForeignScan: show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) @@ -2898,6 +2915,7 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es) case T_IndexOnlyScan: case T_BitmapHeapScan: case T_TidScan: + case T_TidRangeScan: case T_ForeignScan: case T_CustomScan: case T_ModifyTable: diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index cc09895..0152e31 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -28,6 +28,7 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \ nodeValuesscan.o \ nodeCtescan.o nodeNamedtuplestorescan.o nodeWorktablescan.o \ nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \ + nodeTidrangescan.o \ nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \ nodeTableFuncscan.o diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 9e78421..48ab2db 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -52,6 +52,7 @@ #include "executor/nodeSubqueryscan.h" #include "executor/nodeTableFuncscan.h" #include "executor/nodeTidscan.h" +#include "executor/nodeTidrangescan.h" #include "executor/nodeUnique.h" #include "executor/nodeValuesscan.h" #include "executor/nodeWindowAgg.h" @@ -197,6 +198,10 @@ ExecReScan(PlanState *node) ExecReScanTidScan((TidScanState *) node); break; + case T_TidRangeScanState: + ExecReScanTidRangeScan((TidRangeScanState *) node); + break; + case T_SubqueryScanState: ExecReScanSubqueryScan((SubqueryScanState *) node); break; @@ -520,6 +525,7 @@ ExecSupportsBackwardScan(Plan *node) case T_SeqScan: case T_TidScan: + case T_TidRangeScan: case T_FunctionScan: case T_ValuesScan: case T_CteScan: diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index eaed9fb..dec4dac 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -109,6 +109,7 @@ #include "executor/nodeSubqueryscan.h" #include "executor/nodeTableFuncscan.h" #include "executor/nodeTidscan.h" +#include "executor/nodeTidrangescan.h" #include "executor/nodeUnique.h" #include "executor/nodeValuesscan.h" #include "executor/nodeWindowAgg.h" @@ -238,6 +239,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_TidRangeScan: + result = (PlanState *) ExecInitTidRangeScan((TidRangeScan *) node, + estate, eflags); + break; + case T_SubqueryScan: result = (PlanState *) ExecInitSubqueryScan((SubqueryScan *) node, estate, eflags); @@ -632,6 +638,10 @@ ExecEndNode(PlanState *node) ExecEndTidScan((TidScanState *) node); break; + case T_TidRangeScanState: + ExecEndTidRangeScan((TidRangeScanState *) node); + break; + case T_SubqueryScanState: ExecEndSubqueryScan((SubqueryScanState *) node); break; diff --git a/src/backend/executor/nodeTidrangescan.c b/src/backend/executor/nodeTidrangescan.c new file mode 100644 index 0000000..03e62d8 --- /dev/null +++ b/src/backend/executor/nodeTidrangescan.c @@ -0,0 +1,380 @@ +/*------------------------------------------------------------------------- + * + * nodeTidrangescan.c + * Routines to support scanning a range of tids + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/nodeTidrangescan.c + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * + * ExecTidRangeScan scans a relation using a range of tids. + * ExecInitTidRangeScan creates and initializes state info. + * ExecReScanTidRangeScan rescans the tid relation. + * ExecEndTidRangeScan releases all storage. + */ +#include "postgres.h" + +#include "access/relscan.h" +#include "access/sysattr.h" +#include "catalog/pg_type.h" +#include "executor/execdebug.h" +#include "executor/nodeTidrangescan.h" +#include "miscadmin.h" +#include "optimizer/clauses.h" +#include "storage/bufmgr.h" +#include "utils/rel.h" + +static void TidRangeEvalBounds(TidRangeScanState *tidstate, BlockNumber rs_nblocks); +static TupleTableSlot *TidRangeNext(TidRangeScanState *node); +static bool TidRangeRecheck(TidRangeScanState *node, TupleTableSlot *slot); + + +static void +TidRangeEvalBounds(TidRangeScanState *tidstate, BlockNumber rs_nblocks) +{ + ExprContext *econtext = tidstate->ss.ps.ps_ExprContext; + ItemPointer itemptr; + bool isNull; + + if (tidstate->lower_expr) + itemptr = (ItemPointer) + DatumGetPointer(ExecEvalExprSwitchContext(tidstate->lower_expr, + econtext, + &isNull)); + else + isNull = true; + + if (!isNull) + { + tidstate->first_block = ItemPointerGetBlockNumberNoCheck(itemptr); + tidstate->first_tuple = ItemPointerGetOffsetNumberNoCheck(itemptr); + + if (((TidRangeScan *) (tidstate->ss.ps.plan))->lower_strict) + { + tidstate->first_tuple++; + if (tidstate->last_tuple == 0) + tidstate->last_block++; + } + + if (tidstate->first_block > 0 && tidstate->first_block >= rs_nblocks) + { + tidstate->first_block = 0; + tidstate->blocks_to_scan = 0; + return; + } + } + else + { + tidstate->first_block = 0; + tidstate->first_tuple = 0; + } + + Assert(tidstate->first_block == 0 || tidstate->first_block < rs_nblocks); + + if (tidstate->upper_expr) + itemptr = (ItemPointer) + DatumGetPointer(ExecEvalExprSwitchContext(tidstate->upper_expr, + econtext, + &isNull)); + else + isNull = true; + + if (!isNull) + { + tidstate->last_block = ItemPointerGetBlockNumberNoCheck(itemptr); + tidstate->last_tuple = ItemPointerGetOffsetNumberNoCheck(itemptr); + + if (((TidRangeScan *) (tidstate->ss.ps.plan))->upper_strict) + { + /* If decrementing the last_tuple would cause last_block to underflow, don't do it. */ + if (tidstate->last_block == 0 && tidstate->last_tuple == 0) + { + tidstate->first_block = 0; + tidstate->blocks_to_scan = 0; + return; + } + else + { + if (tidstate->last_tuple == 0) + tidstate->last_block--; + tidstate->last_tuple--; + } + } + } + else + { + tidstate->last_block = InvalidBlockNumber; + tidstate->last_tuple = MaxOffsetNumber; + } + + tidstate->blocks_to_scan = BlockNumberIsValid(tidstate->last_block) ? (tidstate->last_block - tidstate->first_block + 1) : (rs_nblocks - tidstate->first_block); +} + +/* ---------------------------------------------------------------- + * TidRangeNext + * + * Retrieve a tuple from the TidRangeScan node's currentRelation + * using a heap scan between the bounds in the TidRangeScanState. + * + * ---------------------------------------------------------------- + */ +static TupleTableSlot * +TidRangeNext(TidRangeScanState *node) +{ + HeapTuple tuple; + HeapScanDesc scandesc; + EState *estate; + ScanDirection direction; + TupleTableSlot *slot; + + /* + * get information from the estate and scan state + */ + scandesc = node->ss.ss_currentScanDesc; + estate = node->ss.ps.state; + direction = estate->es_direction; + if (ScanDirectionIsBackward(((TidRangeScan *) node->ss.ps.plan)->direction)) + { + if (ScanDirectionIsForward(direction)) + direction = BackwardScanDirection; + else if (ScanDirectionIsBackward(direction)) + direction = ForwardScanDirection; + } + slot = node->ss.ss_ScanTupleSlot; + + /* compute bounds and start a new scan, if necessary */ + if (node->first_block == InvalidBlockNumber) + { + if (scandesc == NULL) + { + scandesc = heap_beginscan_strat(node->ss.ss_currentRelation, + estate->es_snapshot, + 0, NULL, + false, false); + node->ss.ss_currentScanDesc = scandesc; + } + + TidRangeEvalBounds(node, scandesc->rs_nblocks); + + heap_setscanlimits(scandesc, node->first_block, node->blocks_to_scan); + printf("set scan limits to %d, %d\n", node->first_block, node->blocks_to_scan); + } + + /* + * get the next tuple from the table + */ + for (;;) + { + BlockNumber block; + OffsetNumber offset; + + tuple = heap_getnext(scandesc, direction); + if (!tuple) + break; + + block = ItemPointerGetBlockNumber(&tuple->t_self); + offset = ItemPointerGetOffsetNumber(&tuple->t_self); + + if (block == node->first_block && offset < node->first_tuple) + continue; + + if (block == node->last_block && offset > node->last_tuple) + continue; + + break; + } + + /* + * save the tuple and the buffer returned to us by the access methods in + * our scan tuple slot and return the slot. Note: we pass 'false' because + * tuples returned by heap_getnext() are pointers onto disk pages and were + * not created with palloc() and so should not be pfree()'d. Note also + * that ExecStoreTuple will increment the refcount of the buffer; the + * refcount will not be dropped until the tuple table slot is cleared. + */ + if (tuple) + ExecStoreTuple(tuple, /* tuple to store */ + slot, /* slot to store in */ + scandesc->rs_cbuf, /* buffer associated with this + * tuple */ + false); /* don't pfree this pointer */ + else + 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 in the direction specified. + * 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 scanning so that the + * "cursor" is positioned before the first qualifying tuple. + * -- tidPtr is -1. + * ---------------------------------------------------------------- + */ +static TupleTableSlot * +ExecTidRangeScan(PlanState *pstate) +{ + TidRangeScanState *node = castNode(TidRangeScanState, pstate); + + return ExecScan(&node->ss, + (ExecScanAccessMtd) TidRangeNext, + (ExecScanRecheckMtd) TidRangeRecheck); +} + +/* ---------------------------------------------------------------- + * ExecReScanTidRangeScan(node) + * ---------------------------------------------------------------- + */ +void +ExecReScanTidRangeScan(TidRangeScanState *node) +{ + HeapScanDesc scan = node->ss.ss_currentScanDesc; + + if (scan != NULL) + heap_rescan(scan, /* scan desc */ + NULL); /* new scan keys */ + + /* mark tid range as not computed yet */ + node->first_block = InvalidBlockNumber; + + ExecScanReScan(&node->ss); +} + +/* ---------------------------------------------------------------- + * ExecEndTidRangeScan + * + * Releases any storage allocated through C routines. + * Returns nothing. + * ---------------------------------------------------------------- + */ +void +ExecEndTidRangeScan(TidRangeScanState *node) +{ + HeapScanDesc scan = node->ss.ss_currentScanDesc; + + /* + * Free the exprcontext + */ + ExecFreeExprContext(&node->ss.ps); + + /* + * clear out tuple table slots + */ + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + ExecClearTuple(node->ss.ss_ScanTupleSlot); + + /* close heap scan */ + if (scan != NULL) + heap_endscan(scan); + + /* + * close the heap relation. + */ + ExecCloseScanRelation(node->ss.ss_currentRelation); +} + +/* ---------------------------------------------------------------- + * ExecInitTidRangeScan + * + * Initializes the tid scan's state information, creates + * scan keys, and opens the base and tid relations. + * + * Parameters: + * node: TidNode node produced by the planner. + * estate: the execution state initialized in InitPlan. + * ---------------------------------------------------------------- + */ +TidRangeScanState * +ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags) +{ + TidRangeScanState *tidstate; + Relation currentRelation; + + /* + * create state structure + */ + tidstate = makeNode(TidRangeScanState); + tidstate->ss.ps.plan = (Plan *) node; + tidstate->ss.ps.state = estate; + tidstate->ss.ps.ExecProcNode = ExecTidRangeScan; + + /* + * Miscellaneous initialization + * + * create expression context for node + */ + ExecAssignExprContext(estate, &tidstate->ss.ps); + + /* + * mark tid range as not computed yet (note that only + * first_block == InvalidBlockNumber is necessary; the + * others are just for consistency) + */ + tidstate->first_block = InvalidBlockNumber; + tidstate->first_tuple = InvalidOffsetNumber; + tidstate->last_block = InvalidBlockNumber; + tidstate->last_tuple = InvalidOffsetNumber; + + /* + * open the base relation and acquire appropriate lock on it. + */ + currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags); + + tidstate->ss.ss_currentRelation = currentRelation; + tidstate->ss.ss_currentScanDesc = NULL; /* no heap scan here */ + + /* + * get the scan type from the relation descriptor. + */ + ExecInitScanTupleSlot(estate, &tidstate->ss, + RelationGetDescr(currentRelation)); + + /* + * Initialize result slot, type and projection. + */ + ExecInitResultTupleSlotTL(estate, &tidstate->ss.ps); + ExecAssignScanProjectionInfo(&tidstate->ss); + + /* + * initialize child expressions + */ + tidstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate); + + tidstate->lower_expr = ExecInitExpr((Expr *) node->lower_bound, &tidstate->ss.ps); + tidstate->upper_expr = ExecInitExpr((Expr *) node->upper_bound, &tidstate->ss.ps); + + /* + * all done. + */ + return tidstate; +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 5f84984..c438058 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -588,6 +588,30 @@ _copyTidScan(const TidScan *from) return newnode; } +static TidRangeScan * +_copyTidRangeScan(const TidRangeScan *from) +{ + TidRangeScan *newnode = makeNode(TidRangeScan); + + /* + * copy node superclass fields + */ + CopyScanFields((const Scan *) from, (Scan *) newnode); + + /* + * copy remainder of node + */ + COPY_NODE_FIELD(tidquals); + COPY_NODE_FIELD(lower_bound); + COPY_NODE_FIELD(upper_bound); + COPY_SCALAR_FIELD(lower_strict); + COPY_SCALAR_FIELD(upper_strict); + COPY_SCALAR_FIELD(direction); + + return newnode; +} + + /* * _copySubqueryScan */ @@ -4842,6 +4866,9 @@ copyObjectImpl(const void *from) case T_TidScan: retval = _copyTidScan(from); break; + case T_TidRangeScan: + retval = _copyTidRangeScan(from); + break; case T_SubqueryScan: retval = _copySubqueryScan(from); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 870dd2e..2cab724 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -619,6 +619,21 @@ _outTidScan(StringInfo str, const TidScan *node) } static void +_outTidRangeScan(StringInfo str, const TidRangeScan *node) +{ + WRITE_NODE_TYPE("TIDRANGESCAN"); + + _outScanInfo(str, (const Scan *) node); + + WRITE_NODE_FIELD(tidquals); + WRITE_NODE_FIELD(lower_bound); + WRITE_NODE_FIELD(upper_bound); + WRITE_BOOL_FIELD(lower_strict); + WRITE_BOOL_FIELD(upper_strict); + WRITE_ENUM_FIELD(direction, ScanDirection); +} + +static void _outSubqueryScan(StringInfo str, const SubqueryScan *node) { WRITE_NODE_TYPE("SUBQUERYSCAN"); @@ -1892,6 +1907,12 @@ _outTidPath(StringInfo str, const TidPath *node) _outPathInfo(str, (const Path *) node); WRITE_NODE_FIELD(tidquals); + WRITE_ENUM_FIELD(method, TidPathMethod); + WRITE_NODE_FIELD(lower_bound); + WRITE_NODE_FIELD(upper_bound); + WRITE_BOOL_FIELD(lower_strict); + WRITE_BOOL_FIELD(upper_strict); + WRITE_ENUM_FIELD(direction, ScanDirection); } static void @@ -3763,6 +3784,9 @@ outNode(StringInfo str, const void *obj) case T_TidScan: _outTidScan(str, obj); break; + case T_TidRangeScan: + _outTidRangeScan(str, obj); + break; case T_SubqueryScan: _outSubqueryScan(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 2317f58..76c65a0 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1834,6 +1834,26 @@ _readTidScan(void) } /* + * _readTidRangeScan + */ +static TidRangeScan * +_readTidRangeScan(void) +{ + READ_LOCALS(TidRangeScan); + + ReadCommonScan(&local_node->scan); + + READ_NODE_FIELD(tidquals); + READ_NODE_FIELD(lower_bound); + READ_NODE_FIELD(upper_bound); + READ_BOOL_FIELD(lower_strict); + READ_BOOL_FIELD(upper_strict); + READ_ENUM_FIELD(direction, ScanDirection); + + READ_DONE(); +} + +/* * _readSubqueryScan */ static SubqueryScan * @@ -2684,6 +2704,8 @@ parseNodeString(void) return_value = _readBitmapHeapScan(); else if (MATCH("TIDSCAN", 7)) return_value = _readTidScan(); + else if (MATCH("TIDRANGESCAN", 12)) + return_value = _readTidRangeScan(); else if (MATCH("SUBQUERYSCAN", 12)) return_value = _readSubqueryScan(); else if (MATCH("FUNCTIONSCAN", 12)) diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 7bf67a0..4a26187 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -76,6 +76,7 @@ #include "access/amapi.h" #include "access/htup_details.h" #include "access/tsmapi.h" +#include "catalog/pg_operator.h" #include "executor/executor.h" #include "executor/nodeHash.h" #include "miscadmin.h" @@ -93,6 +94,7 @@ #include "utils/selfuncs.h" #include "utils/spccache.h" #include "utils/tuplesort.h" +#include "nodes/print.h" #define LOG2(x) (log(x) / 0.693147180559945) @@ -1166,6 +1168,54 @@ cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root) path->path.total_cost = totalCost; } +static void +estimate_tidscan_tuples_and_pages(RelOptInfo *baserel, List *tidquals, TidPathMethod method, Expr *lower_bound, Expr *upper_bound, + int *ntuples_out, int *npages_out, int *nrandom_pages_out, bool *isCurrentOf) { + ListCell *l; + int ntuples = 0; + int npages = 0; + int nrandom_pages = 0; + + if (method == TID_PATH_LIST) + { + foreach(l, tidquals) + { + if (IsA(lfirst(l), ScalarArrayOpExpr)) + { + /* Each element of the array yields 1 tuple */ + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) lfirst(l); + Node *arraynode = (Node *) lsecond(saop->args); + + ntuples += estimate_array_length(arraynode); + nrandom_pages++; + } + else if (IsA(lfirst(l), CurrentOfExpr)) + { + /* CURRENT OF yields 1 tuple */ + *isCurrentOf = true; + ntuples++; + nrandom_pages++; + } + else + { + /* It's just CTID = something, count 1 tuple */ + ntuples++; + nrandom_pages++; + } + } + } + else + { + double selectivity = tid_range_selectivity(baserel, lower_bound, upper_bound); + ntuples += selectivity * baserel->tuples; + npages += selectivity * baserel->pages; + } + + *ntuples_out = ntuples; + *npages_out = npages; + *nrandom_pages_out = nrandom_pages; +} + /* * cost_tidscan * Determines and returns the cost of scanning a relation using TIDs. @@ -1176,7 +1226,8 @@ cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root) */ void cost_tidscan(Path *path, PlannerInfo *root, - RelOptInfo *baserel, List *tidquals, ParamPathInfo *param_info) + RelOptInfo *baserel, List *tidquals, TidPathMethod method, Expr *lower_bound, Expr *upper_bound, + ParamPathInfo *param_info) { Cost startup_cost = 0; Cost run_cost = 0; @@ -1185,8 +1236,10 @@ cost_tidscan(Path *path, PlannerInfo *root, Cost cpu_per_tuple; QualCost tid_qual_cost; int ntuples; - ListCell *l; + int npages; + int nrandom_pages; double spc_random_page_cost; + double spc_seq_page_cost; /* Should only be applied to base relations */ Assert(baserel->relid > 0); @@ -1199,29 +1252,8 @@ cost_tidscan(Path *path, PlannerInfo *root, path->rows = baserel->rows; /* Count how many tuples we expect to retrieve */ - ntuples = 0; - foreach(l, tidquals) - { - if (IsA(lfirst(l), ScalarArrayOpExpr)) - { - /* Each element of the array yields 1 tuple */ - ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) lfirst(l); - Node *arraynode = (Node *) lsecond(saop->args); - - ntuples += estimate_array_length(arraynode); - } - else if (IsA(lfirst(l), CurrentOfExpr)) - { - /* CURRENT OF yields 1 tuple */ - isCurrentOf = true; - ntuples++; - } - else - { - /* It's just CTID = something, count 1 tuple */ - ntuples++; - } - } + estimate_tidscan_tuples_and_pages(baserel, tidquals, method, lower_bound, upper_bound, + &ntuples, &npages, &nrandom_pages, &isCurrentOf); /* * We must force TID scan for WHERE CURRENT OF, because only nodeTidscan.c @@ -1248,10 +1280,11 @@ cost_tidscan(Path *path, PlannerInfo *root, /* fetch estimated page cost for tablespace containing table */ get_tablespace_page_costs(baserel->reltablespace, &spc_random_page_cost, - NULL); + &spc_seq_page_cost); - /* disk costs --- assume each tuple on a different page */ - run_cost += spc_random_page_cost * ntuples; + /* disk costs */ + run_cost += spc_random_page_cost * nrandom_pages; + run_cost += spc_seq_page_cost * npages; /* Add scanning CPU costs */ get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost); diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c index 7a40700..28f455b 100644 --- a/src/backend/optimizer/path/tidpath.c +++ b/src/backend/optimizer/path/tidpath.c @@ -44,34 +44,43 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/restrictinfo.h" +#include "nodes/makefuncs.h" -static bool IsTidEqualClause(OpExpr *node, int varno); static bool IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno); static List *TidQualFromExpr(Node *expr, int varno); -static List *TidQualFromBaseRestrictinfo(RelOptInfo *rel); +static List *TidQualFromBaseRestrictinfo(RelOptInfo *rel, TidPathMethod *method, Expr **lower_bound, Expr **upper_bound, bool *lower_strict, bool *upper_strict); +static bool IsTidVar(Var *var, int varno) +{ + return (var->varattno == SelfItemPointerAttributeNumber && + var->vartype == TIDOID && + var->varno == varno && + var->varlevelsup == 0); +} + /* * Check to see if an opclause is of the form - * CTID = pseudoconstant + * CTID OP pseudoconstant * or - * pseudoconstant = CTID + * pseudoconstant OP CTID + * where OP is the expected comparison operator. * * We check that the CTID Var belongs to relation "varno". That is probably * redundant considering this is only applied to restriction clauses, but * let's be safe. */ static bool -IsTidEqualClause(OpExpr *node, int varno) +IsTidComparison(OpExpr *node, int varno, Oid expected_comparison_operator) { Node *arg1, *arg2, *other; Var *var; - /* Operator must be tideq */ - if (node->opno != TIDEqualOperator) + /* Operator must be the expected one */ + if (node->opno != expected_comparison_operator) return false; if (list_length(node->args) != 2) return false; @@ -110,6 +119,14 @@ IsTidEqualClause(OpExpr *node, int varno) return true; /* success */ } + +#define IsTidEqualClause(node, varno) IsTidComparison(node, varno, TIDEqualOperator) +#define IsTidLTClause(node, varno) IsTidComparison(node, varno, TIDLessOperator) +#define IsTidLEClause(node, varno) IsTidComparison(node, varno, TIDLessEqOperator) +#define IsTidGTClause(node, varno) IsTidComparison(node, varno, TIDGreaterOperator) +#define IsTidGEClause(node, varno) IsTidComparison(node, varno, TIDGreaterEqOperator) + + /* * Check to see if a clause is of the form * CTID = ANY (pseudoconstant_array) @@ -216,14 +233,60 @@ TidQualFromExpr(Node *expr, int varno) return rlst; } +static Node * +TidRangeQualFromExpr(Node *expr, int varno, bool want_lower_bound, Expr **bound, bool *strict) +{ + if (is_opclause(expr)) + { + if (IsTidLTClause((OpExpr *) expr, varno) || IsTidLEClause((OpExpr *) expr, varno) || + (IsTidGTClause((OpExpr *) expr, varno) || IsTidGEClause((OpExpr *) expr, varno))) + { + bool is_lower_bound = IsTidGTClause((OpExpr *) expr, varno) || IsTidGEClause((OpExpr *) expr, varno); + + Node *rightop = get_rightop((Expr *) expr); + Node *leftop = get_leftop((Expr *) expr); + Node *value = rightop; + + if (!IsA(leftop, Var) || !IsTidVar((Var *) leftop, varno)) + { + is_lower_bound = !is_lower_bound; + value = leftop; + } + + if (is_lower_bound == want_lower_bound) + { + *strict = IsTidGTClause((OpExpr *) expr, varno) || IsTidLTClause((OpExpr *) expr, varno); + *bound = (Expr *) value; + return expr; + } + } + } + + return NULL; +} + +static List * +MakeTidRangeQuals(Node *lower_bound_expr, Node *upper_bound_expr) +{ + if (lower_bound_expr && !upper_bound_expr) + return list_make1(lower_bound_expr); + else if (!lower_bound_expr && upper_bound_expr) + return list_make1(upper_bound_expr); + else + return list_make2(lower_bound_expr, upper_bound_expr); +} + /* * Extract a set of CTID conditions from the rel's baserestrictinfo list */ static List * -TidQualFromBaseRestrictinfo(RelOptInfo *rel) +TidQualFromBaseRestrictinfo(RelOptInfo *rel, TidPathMethod *method, + Expr **lower_bound, Expr **upper_bound, bool *lower_strict, bool *upper_strict) { List *rlst = NIL; ListCell *l; + Node *lower_bound_expr = NULL; + Node *upper_bound_expr = NULL; foreach(l, rel->baserestrictinfo) { @@ -236,13 +299,37 @@ TidQualFromBaseRestrictinfo(RelOptInfo *rel) if (!restriction_is_securely_promotable(rinfo, rel)) continue; + /* + * Check if this clause contains a range qual + */ + if (!lower_bound_expr) + lower_bound_expr = TidRangeQualFromExpr((Node *) rinfo->clause, rel->relid, true, lower_bound, lower_strict); + + if (!upper_bound_expr) + upper_bound_expr = TidRangeQualFromExpr((Node *) rinfo->clause, rel->relid, false, upper_bound, upper_strict); + rlst = TidQualFromExpr((Node *) rinfo->clause, rel->relid); if (rlst) break; } + + /* + * If one or both range quals was specified, and there were no equality/in/current-of quals, use them. + */ + if (!rlst && (lower_bound_expr || upper_bound_expr)) + { + rlst = MakeTidRangeQuals(lower_bound_expr, upper_bound_expr); + *method = TID_PATH_RANGE; + } + else if (rlst) + { + *method = TID_PATH_LIST; + } + return rlst; } + /* * create_tidscan_paths * Create paths corresponding to direct TID scans of the given rel. @@ -254,8 +341,15 @@ TidQualFromBaseRestrictinfo(RelOptInfo *rel) void create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) { - Relids required_outer; - List *tidquals; + Relids required_outer; + List *tidquals; + TidPathMethod method = TID_PATH_RANGE; + Expr *lower_bound = NULL; + Expr *upper_bound = NULL; + bool lower_strict = false; + bool upper_strict = false; + List *pathkeys = NULL; + ScanDirection direction = ForwardScanDirection; /* * We don't support pushing join clauses into the quals of a tidscan, but @@ -264,33 +358,42 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) */ required_outer = rel->lateral_relids; - tidquals = TidQualFromBaseRestrictinfo(rel); + tidquals = TidQualFromBaseRestrictinfo(rel, &method, &lower_bound, &upper_bound, &lower_strict, &upper_strict); - if (tidquals) + /* + * Try to determine the best scan direction and create some useful pathkeys. + */ + if (has_useful_pathkeys(root, rel)) { - List *pathkeys = NULL; - ScanDirection direction = ForwardScanDirection; - - if (has_useful_pathkeys(root, rel)) { - /* - * Build path keys corresponding to ORDER BY ctid ASC, and check - * whether they will be useful for this scan. If not, build - * path keys for DESC, and try that; set the direction to - * BackwardScanDirection if so. If neither of them will be - * useful, no path keys will be set. - */ - pathkeys = build_tidscan_pathkeys(root, rel, ForwardScanDirection); - if (!pathkeys_contained_in(pathkeys, root->query_pathkeys)) - { - pathkeys = build_tidscan_pathkeys(root, rel, BackwardScanDirection); - if (pathkeys_contained_in(pathkeys, root->query_pathkeys)) - direction = BackwardScanDirection; - else - pathkeys = NULL; - } + /* + * Build path keys corresponding to ORDER BY ctid ASC, and check + * whether they will be useful for this scan. If not, build + * path keys for DESC, and try that; set the direction to + * BackwardScanDirection if so. If neither of them will be + * useful, no path keys will be set. + */ + pathkeys = build_tidscan_pathkeys(root, rel, ForwardScanDirection); + if (!pathkeys_contained_in(pathkeys, root->query_pathkeys)) + { + pathkeys = build_tidscan_pathkeys(root, rel, BackwardScanDirection); + if (pathkeys_contained_in(pathkeys, root->query_pathkeys)) + direction = BackwardScanDirection; + else + pathkeys = NULL; } + } + + /* + * If there are tidquals or some useful pathkeys were found, then it's + * worth generating a tidscan path. + */ + if (tidquals || pathkeys) + { + /* If we don't have any tidquals, then we MUST create a tid range scan path. */ + Assert(tidquals || method == TID_PATH_RANGE); - add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals, pathkeys, direction, - required_outer)); + add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals, + method, lower_bound, upper_bound, lower_strict, upper_strict, + required_outer, direction, pathkeys)); } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 4e1faa6..e479a0a 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -125,8 +125,8 @@ static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, List **qual, List **indexqual, List **indexECs); static void bitmap_subplan_mark_shared(Plan *plan); static List *flatten_partitioned_rels(List *partitioned_rels); -static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path, - List *tlist, List *scan_clauses); +static Scan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path, + List *tlist, List *scan_clauses); static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path, List *tlist, List *scan_clauses); @@ -186,6 +186,8 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist, Index scanrelid); static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tidquals, ScanDirection direction); +static TidRangeScan *make_tidrangescan(List *qptlist, List *qpqual, Index scanrelid, + List *tidquals, Expr *lower_bound, Expr *upper_bound, bool lower_strict, bool upper_strict, ScanDirection direction); static SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual, Index scanrelid, @@ -371,6 +373,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags) case T_IndexOnlyScan: case T_BitmapHeapScan: case T_TidScan: + case T_TidRangeScan: case T_SubqueryScan: case T_FunctionScan: case T_TableFuncScan: @@ -648,6 +651,7 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags) break; case T_TidScan: + case T_TidRangeScan: plan = (Plan *) create_tidscan_plan(root, (TidPath *) best_path, tlist, @@ -3057,11 +3061,11 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, * Returns a tidscan plan for the base relation scanned by 'best_path' * with restriction clauses 'scan_clauses' and targetlist 'tlist'. */ -static TidScan * +static Scan * create_tidscan_plan(PlannerInfo *root, TidPath *best_path, List *tlist, List *scan_clauses) { - TidScan *scan_plan; + Scan *scan_plan; Index scan_relid = best_path->path.parent->relid; List *tidquals = best_path->tidquals; List *ortidquals; @@ -3090,18 +3094,34 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, * tidquals list has implicit OR semantics. */ ortidquals = tidquals; - if (list_length(ortidquals) > 1) + if (best_path->method == TID_PATH_LIST && list_length(ortidquals) > 1) ortidquals = list_make1(make_orclause(ortidquals)); scan_clauses = list_difference(scan_clauses, ortidquals); - scan_plan = make_tidscan(tlist, - scan_clauses, - scan_relid, - tidquals, - best_path->direction - ); + if (best_path->method == TID_PATH_LIST) + { + scan_plan = make_tidscan(tlist, + scan_clauses, + scan_relid, + tidquals, + best_path->direction + ); + } + else + { + scan_plan = (Scan *) make_tidrangescan(tlist, + scan_clauses, + scan_relid, + tidquals, + best_path->lower_bound, + best_path->upper_bound, + best_path->lower_strict, + best_path->upper_strict, + best_path->direction + ); + } - copy_generic_path_info(&scan_plan->scan.plan, &best_path->path); + copy_generic_path_info(&scan_plan->plan, &best_path->path); return scan_plan; } @@ -5198,6 +5218,36 @@ make_tidscan(List *qptlist, return node; } +static TidRangeScan * +make_tidrangescan(List *qptlist, + List *qpqual, + Index scanrelid, + List *tidquals, + Expr *lower_bound, + Expr *upper_bound, + bool lower_strict, + bool upper_strict, + ScanDirection direction + ) +{ + TidRangeScan *node = makeNode(TidRangeScan); + Plan *plan = &node->scan.plan; + + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = NULL; + plan->righttree = NULL; + node->scan.scanrelid = scanrelid; + node->tidquals = tidquals; + node->lower_bound = lower_bound; + node->upper_bound = upper_bound; + node->lower_strict = lower_strict; + node->upper_strict = upper_strict; + node->direction = direction; + + return node; +} + static SubqueryScan * make_subqueryscan(List *qptlist, List *qpqual, diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 69dd327..854d2d6 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -541,6 +541,21 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) fix_scan_list(root, splan->tidquals, rtoffset); } break; + case T_TidRangeScan: + { + TidRangeScan *splan = (TidRangeScan *) plan; + + splan->scan.scanrelid += rtoffset; + splan->scan.plan.targetlist = + fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + splan->scan.plan.qual = + fix_scan_list(root, splan->scan.plan.qual, rtoffset); + splan->lower_bound = + (OpExpr *) fix_scan_expr(root, (Node *) splan->lower_bound, rtoffset); + splan->upper_bound = + (OpExpr *) fix_scan_expr(root, (Node *) splan->upper_bound, rtoffset); + } + break; case T_SubqueryScan: /* Needs special treatment, see comments below */ return set_subqueryscan_references(root, diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 83008d7..2b917694 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2410,6 +2410,14 @@ finalize_plan(PlannerInfo *root, Plan *plan, context.paramids = bms_add_members(context.paramids, scan_params); break; + case T_TidRangeScan: + finalize_primnode((Node *) ((TidRangeScan *) plan)->lower_bound, + &context); + finalize_primnode((Node *) ((TidRangeScan *) plan)->upper_bound, + &context); + context.paramids = bms_add_members(context.paramids, scan_params); + break; + case T_SubqueryScan: { SubqueryScan *sscan = (SubqueryScan *) plan; diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index e2d51a9..fb8b81f 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1186,12 +1186,12 @@ create_bitmap_or_path(PlannerInfo *root, */ TidPath * create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals, - List *pathkeys, ScanDirection direction, - Relids required_outer) + TidPathMethod method, Expr *lower_bound, Expr *upper_bound, bool lower_strict, bool upper_strict, + Relids required_outer, ScanDirection direction, List *pathkeys) { TidPath *pathnode = makeNode(TidPath); - pathnode->path.pathtype = T_TidScan; + pathnode->path.pathtype = (method == TID_PATH_LIST) ? T_TidScan : T_TidRangeScan; pathnode->path.parent = rel; pathnode->path.pathtarget = rel->reltarget; pathnode->path.param_info = get_baserel_parampathinfo(root, rel, @@ -1202,9 +1202,14 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals, pathnode->path.pathkeys = pathkeys; pathnode->tidquals = tidquals; + pathnode->method = method; + pathnode->lower_bound = lower_bound; + pathnode->upper_bound = upper_bound; + pathnode->lower_strict = lower_strict; + pathnode->upper_strict = upper_strict; pathnode->direction = direction; - cost_tidscan(&pathnode->path, root, rel, tidquals, + cost_tidscan(&pathnode->path, root, rel, tidquals, method, lower_bound, upper_bound, pathnode->path.param_info); return pathnode; diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index f1c78ff..8c87d28 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -8219,3 +8219,27 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, *indexPages = index->pages; } + +static BlockNumber +get_block_number_from_tid_qual(Expr *qual, BlockNumber default_when_missing) +{ + if (qual && IsA(qual, Const)) { + Const *con = (Const *) qual; + ItemPointer itemptr = (ItemPointer) DatumGetPointer(con->constvalue); + return ItemPointerGetBlockNumberNoCheck(itemptr); + } + else + { + return default_when_missing; + } +} + +double +tid_range_selectivity(RelOptInfo *rel, Expr *lower_qual, Expr *upper_qual) +{ + BlockNumber lower_block = get_block_number_from_tid_qual(lower_qual, 0); + BlockNumber upper_block = get_block_number_from_tid_qual(upper_qual, rel->pages); + + double selectivity = (upper_block - lower_block) / ((double) rel->pages + 1); + return Max(0.0, Min(1.0, selectivity)); +} diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 31e7d61..cdd2cd3 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -160,11 +160,11 @@ oprname => '>', oprleft => 'tid', oprright => 'tid', oprresult => 'bool', oprcom => '<(tid,tid)', oprnegate => '<=(tid,tid)', oprcode => 'tidgt', oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' }, -{ oid => '2801', descr => 'less than or equal', +{ oid => '2801', oid_symbol => 'TIDLessEqOperator', descr => 'less than or equal', oprname => '<=', oprleft => 'tid', oprright => 'tid', oprresult => 'bool', oprcom => '>=(tid,tid)', oprnegate => '>(tid,tid)', oprcode => 'tidle', oprrest => 'scalarlesel', oprjoin => 'scalarlejoinsel' }, -{ oid => '2802', descr => 'greater than or equal', +{ oid => '2802', oid_symbol => 'TIDGreaterEqOperator', descr => 'greater than or equal', oprname => '>=', oprleft => 'tid', oprright => 'tid', oprresult => 'bool', oprcom => '<=(tid,tid)', oprnegate => '<(tid,tid)', oprcode => 'tidge', oprrest => 'scalargesel', oprjoin => 'scalargejoinsel' }, diff --git a/src/include/executor/nodeTidrangescan.h b/src/include/executor/nodeTidrangescan.h new file mode 100644 index 0000000..d5ad2e1 --- /dev/null +++ b/src/include/executor/nodeTidrangescan.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * nodeTidrangescan.h + * + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/nodeTidscan.h + * + *------------------------------------------------------------------------- + */ +#ifndef NODETIDRANGESCAN_H +#define NODETIDRANGESCAN_H + +#include "nodes/execnodes.h" + +extern TidRangeScanState *ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags); +extern void ExecEndTidRangeScan(TidRangeScanState *node); +extern void ExecReScanTidRangeScan(TidRangeScanState *node); + +#endif /* NODETIDRANGESCAN_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 018f50b..a998bfb 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1490,6 +1490,18 @@ typedef struct TidScanState HeapTupleData tss_htup; } TidScanState; +typedef struct TidRangeScanState +{ + ScanState ss; /* its first field is NodeTag */ + ExprState *lower_expr; + ExprState *upper_expr; + BlockNumber first_block; + OffsetNumber first_tuple; + BlockNumber last_block; + OffsetNumber last_tuple; + BlockNumber blocks_to_scan; +} TidRangeScanState; + /* ---------------- * SubqueryScanState information * diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 697d3d7..e983fb8 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -59,6 +59,7 @@ typedef enum NodeTag T_BitmapIndexScan, T_BitmapHeapScan, T_TidScan, + T_TidRangeScan, T_SubqueryScan, T_FunctionScan, T_ValuesScan, @@ -115,6 +116,7 @@ typedef enum NodeTag T_BitmapIndexScanState, T_BitmapHeapScanState, T_TidScanState, + T_TidRangeScanState, T_SubqueryScanState, T_FunctionScanState, T_TableFuncScanState, diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 96d30aa..a0165e4 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -496,6 +496,21 @@ typedef struct TidScan } TidScan; /* ---------------- + * tid range scan node + * ---------------- + */ +typedef struct TidRangeScan +{ + Scan scan; + List *tidquals; + Expr *lower_bound; + Expr *upper_bound; + bool lower_strict; + bool upper_strict; + ScanDirection direction; +} TidRangeScan; + +/* ---------------- * subquery scan node * * SubqueryScan is for scanning the output of a sub-query in the range table. diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index cf4839d..f348c38 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1229,10 +1229,21 @@ typedef struct BitmapOrPath * "CTID = pseudoconstant" or "CTID = ANY(pseudoconstant_array)". * Note they are bare expressions, not RestrictInfos. */ +typedef enum +{ + TID_PATH_LIST, /* tidquals is a list of CTID = ?, CTID IN (?), with OR-semantics */ + TID_PATH_RANGE /* tidquals is a list of CTID > ?, CTID < ?, with AND-semantics */ +} TidPathMethod; + typedef struct TidPath { - Path path; - List *tidquals; /* qual(s) involving CTID = something */ + Path path; + List *tidquals; + TidPathMethod method; + Expr *lower_bound; + Expr *upper_bound; + bool lower_strict; + bool upper_strict; ScanDirection direction; } TidPath; diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 77ca7ff..d8f7825 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -90,7 +90,8 @@ extern void cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root); extern void cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root); extern void cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec); extern void cost_tidscan(Path *path, PlannerInfo *root, - RelOptInfo *baserel, List *tidquals, ParamPathInfo *param_info); + RelOptInfo *baserel, List *tidquals, TidPathMethod method, Expr *lower_bound, Expr *upper_bound, + ParamPathInfo *param_info); extern void cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root, RelOptInfo *baserel, ParamPathInfo *param_info); extern void cost_functionscan(Path *path, PlannerInfo *root, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index a0a88a5..32867e7 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -63,8 +63,9 @@ extern BitmapOrPath *create_bitmap_or_path(PlannerInfo *root, RelOptInfo *rel, List *bitmapquals); extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, - List *tidquals, List *pathkeys, ScanDirection direction, - Relids required_outer); + List *tidquals, TidPathMethod method, + Expr *lower_bound, Expr *upper_bound, bool lower_strict, bool upper_strict, + Relids required_outer, ScanDirection direction, List *pathkeys); extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, List *partial_subpaths, Relids required_outer, diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index 95e4428..cf5c090 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -227,4 +227,7 @@ extern Selectivity scalararraysel_containment(PlannerInfo *root, Oid elemtype, bool isEquality, bool useOr, int varRelid); + +extern double tid_range_selectivity(RelOptInfo *rel, Expr *lower_qual, Expr *upper_qual); + #endif /* SELFUNCS_H */ diff --git a/src/test/regress/expected/tidrangescan.out b/src/test/regress/expected/tidrangescan.out new file mode 100644 index 0000000..573e769 --- /dev/null +++ b/src/test/regress/expected/tidrangescan.out @@ -0,0 +1,229 @@ +-- tests for tidrangescans +CREATE TABLE tidrangescan(id integer, data text); +INSERT INTO tidrangescan SELECT i,'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' FROM generate_series(1,1000) AS s(i); +DELETE FROM tidrangescan WHERE substring(ctid::text from ',(\d+)\)')::integer > 10 OR substring(ctid::text from '\((\d+),')::integer >= 10;; +VACUUM tidrangescan; +-- range scans with upper bound +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid < '(1, 0)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid < '(1,0)'::tid) +(2 rows) + +SELECT ctid, data FROM tidrangescan WHERE ctid < '(1, 0)'; + ctid | data +--------+---------------------------------------------------------------------------------- + (0,1) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,2) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,3) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,4) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,5) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,6) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,7) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,8) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,9) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,10) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +(10 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid <= '(1, 5)'; + QUERY PLAN +------------------------------------ + Tid Range Scan on tidrangescan + TID Cond: (ctid <= '(1,5)'::tid) +(2 rows) + +SELECT ctid, data FROM tidrangescan WHERE ctid <= '(1, 5)'; + ctid | data +--------+---------------------------------------------------------------------------------- + (0,1) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,2) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,3) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,4) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,5) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,6) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,7) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,8) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,9) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (0,10) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (1,1) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (1,2) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (1,3) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (1,4) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (1,5) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +(15 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid < '(0, 0)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid < '(0,0)'::tid) +(2 rows) + +SELECT ctid, data FROM tidrangescan WHERE ctid < '(0, 0)'; + ctid | data +------+------ +(0 rows) + +-- range scans with lower bound +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid > '(9, 8)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid > '(9,8)'::tid) +(2 rows) + +SELECT ctid, data FROM tidrangescan WHERE ctid > '(9, 8)'; + ctid | data +--------+---------------------------------------------------------------------------------- + (9,9) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (9,10) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid >= '(9, 8)'; + QUERY PLAN +------------------------------------ + Tid Range Scan on tidrangescan + TID Cond: (ctid >= '(9,8)'::tid) +(2 rows) + +SELECT ctid, data FROM tidrangescan WHERE ctid >= '(9, 8)'; + ctid | data +--------+---------------------------------------------------------------------------------- + (9,8) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (9,9) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + (9,10) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +(3 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid >= '(100, 0)'; + QUERY PLAN +-------------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid >= '(100,0)'::tid) +(2 rows) + +SELECT ctid, data FROM tidrangescan WHERE ctid >= '(100, 0)'; + ctid | data +------+------ +(0 rows) + +-- ordering with no quals should use tid range scan +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan ORDER BY ctid ASC; + QUERY PLAN +-------------------------------- + Tid Range Scan on tidrangescan +(1 row) + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan ORDER BY ctid DESC; + QUERY PLAN +----------------------------------------- + Tid Range Scan Backward on tidrangescan +(1 row) + +-- min/max +EXPLAIN (COSTS OFF) +SELECT MIN(ctid) FROM tidrangescan; + QUERY PLAN +---------------------------------------------- + Result + InitPlan 1 (returns $0) + -> Limit + -> Tid Range Scan on tidrangescan + Filter: (ctid IS NOT NULL) +(5 rows) + +SELECT MIN(ctid) FROM tidrangescan; + min +------- + (0,1) +(1 row) + +EXPLAIN (COSTS OFF) +SELECT MAX(ctid) FROM tidrangescan; + QUERY PLAN +------------------------------------------------------- + Result + InitPlan 1 (returns $0) + -> Limit + -> Tid Range Scan Backward on tidrangescan + Filter: (ctid IS NOT NULL) +(5 rows) + +SELECT MAX(ctid) FROM tidrangescan; + max +-------- + (9,10) +(1 row) + +EXPLAIN (COSTS OFF) +SELECT MIN(ctid) FROM tidrangescan WHERE ctid > '(5,0)'; + QUERY PLAN +------------------------------------------------- + Result + InitPlan 1 (returns $0) + -> Limit + -> Tid Range Scan on tidrangescan + TID Cond: (ctid > '(5,0)'::tid) + Filter: (ctid IS NOT NULL) +(6 rows) + +SELECT MIN(ctid) FROM tidrangescan WHERE ctid > '(5,0)'; + min +------- + (5,1) +(1 row) + +EXPLAIN (COSTS OFF) +SELECT MAX(ctid) FROM tidrangescan WHERE ctid < '(5,0)'; + QUERY PLAN +------------------------------------------------------- + Result + InitPlan 1 (returns $0) + -> Limit + -> Tid Range Scan Backward on tidrangescan + TID Cond: (ctid < '(5,0)'::tid) + Filter: (ctid IS NOT NULL) +(6 rows) + +SELECT MAX(ctid) FROM tidrangescan WHERE ctid < '(5,0)'; + max +-------- + (4,10) +(1 row) + +-- empty table +CREATE TABLE tidrangescan_empty(id integer, data text); +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan_empty WHERE ctid < '(1, 0)'; + QUERY PLAN +-------------------------------------- + Tid Range Scan on tidrangescan_empty + TID Cond: (ctid < '(1,0)'::tid) +(2 rows) + +SELECT ctid, data FROM tidrangescan_empty WHERE ctid < '(1, 0)'; + ctid | data +------+------ +(0 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan_empty WHERE ctid > '(9, 0)'; + QUERY PLAN +-------------------------------------- + Tid Range Scan on tidrangescan_empty + TID Cond: (ctid > '(9,0)'::tid) +(2 rows) + +SELECT ctid, data FROM tidrangescan_empty WHERE ctid > '(9, 0)'; + ctid | data +------+------ +(0 rows) + diff --git a/src/test/regress/expected/tidscan.out b/src/test/regress/expected/tidscan.out index 7eebe77..e0ec664 100644 --- a/src/test/regress/expected/tidscan.out +++ b/src/test/regress/expected/tidscan.out @@ -116,6 +116,25 @@ FETCH FIRST FROM c; (1 row) ROLLBACK; +-- make sure that tid scan is chosen rather than tid range scan, if there are equality/in quals as well as range quals +EXPLAIN (COSTS OFF) +SELECT ctid, * FROM tidscan WHERE ctid = '(0,1)' AND ctid < '(3,0)'; + QUERY PLAN +----------------------------------- + Tid Scan on tidscan + TID Cond: (ctid = '(0,1)'::tid) + Filter: (ctid < '(3,0)'::tid) +(3 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]) AND ctid < '(3,0)'; + QUERY PLAN +------------------------------------------------------- + Tid Scan on tidscan + TID Cond: (ctid = ANY ('{"(0,1)","(0,2)"}'::tid[])) + Filter: (ctid < '(3,0)'::tid) +(3 rows) + -- check that ordering on a tidscan doesn't require a sort EXPLAIN (COSTS OFF) SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,2)', '(0,1)', '(0,3)']::tid[]) ORDER BY ctid; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 16f979c..517a469 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview # ---------- # Another group of parallel tests # ---------- -test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext +test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan tidrangescan stats_ext # rules cannot run concurrently with any test that creates a view test: rules psql_crosstab amutils diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 42632be..c97f1c6 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -134,6 +134,7 @@ test: misc_functions test: sysviews test: tsrf test: tidscan +test: tidrangescan test: stats_ext test: rules test: psql_crosstab diff --git a/src/test/regress/sql/tidrangescan.sql b/src/test/regress/sql/tidrangescan.sql new file mode 100644 index 0000000..de132b8 --- /dev/null +++ b/src/test/regress/sql/tidrangescan.sql @@ -0,0 +1,68 @@ +-- tests for tidrangescans + +CREATE TABLE tidrangescan(id integer, data text); + +INSERT INTO tidrangescan SELECT i,'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' FROM generate_series(1,1000) AS s(i); +DELETE FROM tidrangescan WHERE substring(ctid::text from ',(\d+)\)')::integer > 10 OR substring(ctid::text from '\((\d+),')::integer >= 10;; +VACUUM tidrangescan; + +-- range scans with upper bound +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid < '(1, 0)'; +SELECT ctid, data FROM tidrangescan WHERE ctid < '(1, 0)'; + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid <= '(1, 5)'; +SELECT ctid, data FROM tidrangescan WHERE ctid <= '(1, 5)'; + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid < '(0, 0)'; +SELECT ctid, data FROM tidrangescan WHERE ctid < '(0, 0)'; + +-- range scans with lower bound +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid > '(9, 8)'; +SELECT ctid, data FROM tidrangescan WHERE ctid > '(9, 8)'; + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid >= '(9, 8)'; +SELECT ctid, data FROM tidrangescan WHERE ctid >= '(9, 8)'; + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan WHERE ctid >= '(100, 0)'; +SELECT ctid, data FROM tidrangescan WHERE ctid >= '(100, 0)'; + +-- ordering with no quals should use tid range scan +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan ORDER BY ctid ASC; + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan ORDER BY ctid DESC; + +-- min/max +EXPLAIN (COSTS OFF) +SELECT MIN(ctid) FROM tidrangescan; +SELECT MIN(ctid) FROM tidrangescan; + +EXPLAIN (COSTS OFF) +SELECT MAX(ctid) FROM tidrangescan; +SELECT MAX(ctid) FROM tidrangescan; + +EXPLAIN (COSTS OFF) +SELECT MIN(ctid) FROM tidrangescan WHERE ctid > '(5,0)'; +SELECT MIN(ctid) FROM tidrangescan WHERE ctid > '(5,0)'; + +EXPLAIN (COSTS OFF) +SELECT MAX(ctid) FROM tidrangescan WHERE ctid < '(5,0)'; +SELECT MAX(ctid) FROM tidrangescan WHERE ctid < '(5,0)'; + +-- empty table +CREATE TABLE tidrangescan_empty(id integer, data text); + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan_empty WHERE ctid < '(1, 0)'; +SELECT ctid, data FROM tidrangescan_empty WHERE ctid < '(1, 0)'; + +EXPLAIN (COSTS OFF) +SELECT ctid, data FROM tidrangescan_empty WHERE ctid > '(9, 0)'; +SELECT ctid, data FROM tidrangescan_empty WHERE ctid > '(9, 0)'; diff --git a/src/test/regress/sql/tidscan.sql b/src/test/regress/sql/tidscan.sql index 5237f06..05fef3c 100644 --- a/src/test/regress/sql/tidscan.sql +++ b/src/test/regress/sql/tidscan.sql @@ -43,6 +43,12 @@ FETCH BACKWARD 1 FROM c; FETCH FIRST FROM c; ROLLBACK; +-- make sure that tid scan is chosen rather than tid range scan, if there are equality/in quals as well as range quals +EXPLAIN (COSTS OFF) +SELECT ctid, * FROM tidscan WHERE ctid = '(0,1)' AND ctid < '(3,0)'; +EXPLAIN (COSTS OFF) +SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]) AND ctid < '(3,0)'; + -- check that ordering on a tidscan doesn't require a sort EXPLAIN (COSTS OFF) SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,2)', '(0,1)', '(0,3)']::tid[]) ORDER BY ctid;