02_tid_range_scan.patch
application/octet-stream
Filename: 02_tid_range_scan.patch
Type: application/octet-stream
Part: 1
Message:
Tid scan improvements
Patch
Format: unified
| File | + | − |
|---|---|---|
| src/backend/access/heap/heapam.c | 24 | 8 |
| src/backend/commands/explain.c | 34 | 16 |
| src/backend/executor/execAmi.c | 6 | 0 |
| src/backend/executor/execProcnode.c | 10 | 0 |
| src/backend/executor/Makefile | 1 | 0 |
| src/backend/executor/nodeTidrangescan.c | 380 | 0 |
| src/backend/nodes/copyfuncs.c | 27 | 0 |
| src/backend/nodes/outfuncs.c | 24 | 0 |
| src/backend/nodes/readfuncs.c | 22 | 0 |
| src/backend/optimizer/path/costsize.c | 61 | 28 |
| src/backend/optimizer/path/tidpath.c | 137 | 34 |
| src/backend/optimizer/plan/createplan.c | 62 | 12 |
| src/backend/optimizer/plan/setrefs.c | 15 | 0 |
| src/backend/optimizer/plan/subselect.c | 8 | 0 |
| src/backend/optimizer/util/pathnode.c | 9 | 4 |
| src/backend/utils/adt/selfuncs.c | 24 | 0 |
| src/include/catalog/pg_operator.dat | 2 | 2 |
| src/include/executor/nodeTidrangescan.h | 23 | 0 |
| src/include/nodes/execnodes.h | 12 | 0 |
| src/include/nodes/nodes.h | 2 | 0 |
| src/include/nodes/plannodes.h | 15 | 0 |
| src/include/nodes/relation.h | 13 | 2 |
| src/include/optimizer/cost.h | 2 | 1 |
| src/include/optimizer/pathnode.h | 3 | 2 |
| src/include/utils/selfuncs.h | 3 | 0 |
| src/test/regress/expected/tidrangescan.out | 229 | 0 |
| src/test/regress/expected/tidscan.out | 19 | 0 |
| src/test/regress/parallel_schedule | 1 | 1 |
| src/test/regress/serial_schedule | 1 | 0 |
| src/test/regress/sql/tidrangescan.sql | 68 | 0 |
| src/test/regress/sql/tidscan.sql | 6 | 0 |
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;