Thread

  1. [PATCH] v4 parallel tid range scan: improve regression tests

    Cary Huang <cary.huang@highgo.ca> — 2024-05-14T21:14:34Z

    ---
     src/backend/access/heap/heapam.c           |  14 +++
     src/backend/access/table/tableam.c         |  31 +++++-
     src/backend/executor/execParallel.c        |  20 ++++
     src/backend/executor/nodeTidrangescan.c    |  81 ++++++++++++++++
     src/backend/optimizer/path/costsize.c      |  15 +++
     src/backend/optimizer/path/tidpath.c       |  18 +++-
     src/backend/optimizer/util/pathnode.c      |   7 +-
     src/include/access/relscan.h               |   1 +
     src/include/access/tableam.h               |  10 ++
     src/include/executor/nodeTidrangescan.h    |   7 ++
     src/include/nodes/execnodes.h              |   1 +
     src/include/optimizer/pathnode.h           |   3 +-
     src/test/regress/expected/tidrangescan.out | 106 +++++++++++++++++++++
     src/test/regress/sql/tidrangescan.sql      |  45 +++++++++
     14 files changed, 353 insertions(+), 6 deletions(-)
    
    diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
    index 4be0dee4de..a696cba458 100644
    --- a/src/backend/access/heap/heapam.c
    +++ b/src/backend/access/heap/heapam.c
    @@ -307,6 +307,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
     	 * results for a non-MVCC snapshot, the caller must hold some higher-level
     	 * lock that ensures the interesting tuple(s) won't change.)
     	 */
    +
     	if (scan->rs_base.rs_parallel != NULL)
     	{
     		bpscan = (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel;
    @@ -1391,6 +1392,19 @@ heap_set_tidrange(TableScanDesc sscan, ItemPointer mintid,
     	/* Set the start block and number of blocks to scan */
     	heap_setscanlimits(sscan, startBlk, numBlks);
     
    +	/*
    +	 * if parallel mode is used, store startblock and numblocks in parallel
    +	 * scan descriptor as well
    +	 */
    +	if (scan->rs_base.rs_parallel != NULL)
    +	{
    +		ParallelBlockTableScanDesc bpscan = NULL;
    +
    +		bpscan = (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel;
    +		bpscan->phs_startblock = scan->rs_startblock;
    +		bpscan->phs_numblock = scan->rs_numblocks;
    +	}
    +
     	/* Finally, set the TID range in sscan */
     	ItemPointerCopy(&lowestItem, &sscan->rs_mintid);
     	ItemPointerCopy(&highestItem, &sscan->rs_maxtid);
    diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
    index e57a0b7ea3..61959d2b7d 100644
    --- a/src/backend/access/table/tableam.c
    +++ b/src/backend/access/table/tableam.c
    @@ -187,6 +187,34 @@ table_beginscan_parallel(Relation relation, ParallelTableScanDesc pscan)
     											pscan, flags);
     }
     
    +TableScanDesc
    +table_beginscan_parallel_tidrange(Relation relation, ParallelTableScanDesc pscan)
    +{
    +	Snapshot	snapshot;
    +	uint32		flags = SO_TYPE_TIDRANGESCAN | SO_ALLOW_PAGEMODE;
    +	TableScanDesc sscan;
    +
    +	Assert(RelationGetRelid(relation) == pscan->phs_relid);
    +
    +	if (!pscan->phs_snapshot_any)
    +	{
    +		/* Snapshot was serialized -- restore it */
    +		snapshot = RestoreSnapshot((char *) pscan + pscan->phs_snapshot_off);
    +		RegisterSnapshot(snapshot);
    +		flags |= SO_TEMP_SNAPSHOT;
    +	}
    +	else
    +	{
    +		/* SnapshotAny passed by caller (not serialized) */
    +		snapshot = SnapshotAny;
    +	}
    +
    +	sscan = relation->rd_tableam->scan_begin(relation, snapshot, 0, NULL,
    +											 pscan, flags);
    +
    +	return sscan;
    +}
    +
     
     /* ----------------------------------------------------------------------------
      * Index scan related functions.
    @@ -576,7 +604,8 @@ table_block_parallelscan_nextpage(Relation rel,
     		pbscanwork->phsw_chunk_remaining = pbscanwork->phsw_chunk_size - 1;
     	}
     
    -	if (nallocated >= pbscan->phs_nblocks)
    +	if (nallocated >= pbscan->phs_nblocks || (pbscan->phs_numblock != 0 &&
    +		nallocated >= pbscan->phs_numblock))
     		page = InvalidBlockNumber;	/* all blocks have been allocated */
     	else
     		page = (nallocated + pbscan->phs_startblock) % pbscan->phs_nblocks;
    diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
    index 8c53d1834e..e4733ca5a3 100644
    --- a/src/backend/executor/execParallel.c
    +++ b/src/backend/executor/execParallel.c
    @@ -40,6 +40,7 @@
     #include "executor/nodeSort.h"
     #include "executor/nodeSubplan.h"
     #include "executor/tqueue.h"
    +#include "executor/nodeTidrangescan.h"
     #include "jit/jit.h"
     #include "nodes/nodeFuncs.h"
     #include "pgstat.h"
    @@ -296,6 +297,11 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
     			/* even when not parallel-aware, for EXPLAIN ANALYZE */
     			ExecMemoizeEstimate((MemoizeState *) planstate, e->pcxt);
     			break;
    +		case T_TidRangeScanState:
    +			if (planstate->plan->parallel_aware)
    +				ExecTidRangeScanEstimate((TidRangeScanState *) planstate,
    +									e->pcxt);
    +			break;
     		default:
     			break;
     	}
    @@ -520,6 +526,11 @@ ExecParallelInitializeDSM(PlanState *planstate,
     			/* even when not parallel-aware, for EXPLAIN ANALYZE */
     			ExecMemoizeInitializeDSM((MemoizeState *) planstate, d->pcxt);
     			break;
    +		case T_TidRangeScanState:
    +			if (planstate->plan->parallel_aware)
    +				ExecTidRangeScanInitializeDSM((TidRangeScanState *) planstate,
    +										 d->pcxt);
    +			break;
     		default:
     			break;
     	}
    @@ -1006,6 +1017,11 @@ ExecParallelReInitializeDSM(PlanState *planstate,
     		case T_MemoizeState:
     			/* these nodes have DSM state, but no reinitialization is required */
     			break;
    +		case T_TidRangeScanState:
    +			if (planstate->plan->parallel_aware)
    +				ExecTidRangeScanReInitializeDSM((TidRangeScanState *) planstate,
    +										   pcxt);
    +			break;
     
     		default:
     			break;
    @@ -1372,6 +1388,10 @@ ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pwcxt)
     			/* even when not parallel-aware, for EXPLAIN ANALYZE */
     			ExecMemoizeInitializeWorker((MemoizeState *) planstate, pwcxt);
     			break;
    +		case T_TidRangeScanState:
    +			if (planstate->plan->parallel_aware)
    +				ExecTidRangeScanInitializeWorker((TidRangeScanState *) planstate, pwcxt);
    +			break;
     		default:
     			break;
     	}
    diff --git a/src/backend/executor/nodeTidrangescan.c b/src/backend/executor/nodeTidrangescan.c
    index 9aa7683d7e..b1553b990d 100644
    --- a/src/backend/executor/nodeTidrangescan.c
    +++ b/src/backend/executor/nodeTidrangescan.c
    @@ -403,3 +403,84 @@ ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags)
     	 */
     	return tidrangestate;
     }
    +/* ----------------------------------------------------------------
    + *						Parallel Scan Support
    + * ----------------------------------------------------------------
    + */
    +
    +/* ----------------------------------------------------------------
    + *		ExecTidRangeScanEstimate
    + *
    + *		Compute the amount of space we'll need in the parallel
    + *		query DSM, and inform pcxt->estimator about our needs.
    + * ----------------------------------------------------------------
    + */
    +void
    +ExecTidRangeScanEstimate(TidRangeScanState *node,
    +					ParallelContext *pcxt)
    +{
    +	EState	   *estate = node->ss.ps.state;
    +
    +	node->pscan_len = table_parallelscan_estimate(node->ss.ss_currentRelation,
    +												  estate->es_snapshot);
    +	shm_toc_estimate_chunk(&pcxt->estimator, node->pscan_len);
    +	shm_toc_estimate_keys(&pcxt->estimator, 1);
    +}
    +
    +/* ----------------------------------------------------------------
    + *		ExecTidRangeScanInitializeDSM
    + *
    + *		Set up a parallel heap scan descriptor.
    + * ----------------------------------------------------------------
    + */
    +void
    +ExecTidRangeScanInitializeDSM(TidRangeScanState *node,
    +						 ParallelContext *pcxt)
    +{
    +	EState	   *estate = node->ss.ps.state;
    +	ParallelTableScanDesc pscan;
    +
    +	pscan = shm_toc_allocate(pcxt->toc, node->pscan_len);
    +	table_parallelscan_initialize(node->ss.ss_currentRelation,
    +								  pscan,
    +								  estate->es_snapshot);
    +	/* disable syncscan in parallel tid range scan */
    +	pscan->phs_syncscan = false;
    +	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
    +	node->ss.ss_currentScanDesc =
    +		table_beginscan_parallel_tidrange(node->ss.ss_currentRelation, pscan);
    +}
    +
    +/* ----------------------------------------------------------------
    + *		ExecTidRangeScanReInitializeDSM
    + *
    + *		Reset shared state before beginning a fresh scan.
    + * ----------------------------------------------------------------
    + */
    +void
    +ExecTidRangeScanReInitializeDSM(TidRangeScanState *node,
    +						   ParallelContext *pcxt)
    +{
    +	ParallelTableScanDesc pscan;
    +
    +	pscan = node->ss.ss_currentScanDesc->rs_parallel;
    +	table_parallelscan_reinitialize(node->ss.ss_currentRelation, pscan);
    +
    +}
    +
    +/* ----------------------------------------------------------------
    + *		ExecSeqScanInitializeWorker
    + *
    + *		Copy relevant information from TOC into planstate.
    + * ----------------------------------------------------------------
    + */
    +void
    +ExecTidRangeScanInitializeWorker(TidRangeScanState *node,
    +							ParallelWorkerContext *pwcxt)
    +{
    +	ParallelTableScanDesc pscan;
    +
    +	pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
    +	node->ss.ss_currentScanDesc =
    +		table_beginscan_parallel_tidrange(node->ss.ss_currentRelation, pscan);
    +}
    diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
    index ee23ed7835..fee603d048 100644
    --- a/src/backend/optimizer/path/costsize.c
    +++ b/src/backend/optimizer/path/costsize.c
    @@ -1435,6 +1435,21 @@ cost_tidrangescan(Path *path, PlannerInfo *root,
     	startup_cost += path->pathtarget->cost.startup;
     	run_cost += path->pathtarget->cost.per_tuple * path->rows;
     
    +	/* Adjust costing for parallelism, if used. */
    +	if (path->parallel_workers > 0)
    +	{
    +		double		parallel_divisor = get_parallel_divisor(path);
    +
    +		/* The CPU cost is divided among all the workers. */
    +		run_cost /= parallel_divisor;
    +
    +		/*
    +		 * In the case of a parallel plan, the row count needs to represent
    +		 * the number of tuples processed per worker.
    +		 */
    +		path->rows = clamp_row_est(path->rows / parallel_divisor);
    +	}
    +
     	path->startup_cost = startup_cost;
     	path->total_cost = startup_cost + run_cost;
     }
    diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
    index 2ae5ddfe43..3c52ef911e 100644
    --- a/src/backend/optimizer/path/tidpath.c
    +++ b/src/backend/optimizer/path/tidpath.c
    @@ -46,6 +46,7 @@
     #include "optimizer/pathnode.h"
     #include "optimizer/paths.h"
     #include "optimizer/restrictinfo.h"
    +#include "optimizer/cost.h"
     
     
     /*
    @@ -496,7 +497,22 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
     
     		add_path(rel, (Path *) create_tidrangescan_path(root, rel,
     														tidrangequals,
    -														required_outer));
    +														required_outer,
    +														0));
    +
    +		/* If appropriate, consider parallel tid range scan */
    +		if (rel->consider_parallel && required_outer == NULL)
    +		{
    +			int			parallel_workers;
    +
    +			parallel_workers = compute_parallel_worker(rel, rel->pages, -1,
    +													   max_parallel_workers_per_gather);
    +			if (parallel_workers > 0)
    +			{
    +				add_partial_path(rel, (Path *) create_tidrangescan_path(root, rel, tidrangequals,
    +								required_outer, parallel_workers));
    +			}
    +		}
     	}
     
     	/*
    diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
    index 3cf1dac087..7ceeaf8688 100644
    --- a/src/backend/optimizer/util/pathnode.c
    +++ b/src/backend/optimizer/util/pathnode.c
    @@ -1206,7 +1206,8 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals,
      */
     TidRangePath *
     create_tidrangescan_path(PlannerInfo *root, RelOptInfo *rel,
    -						 List *tidrangequals, Relids required_outer)
    +						 List *tidrangequals, Relids required_outer,
    +						 int parallel_workers)
     {
     	TidRangePath *pathnode = makeNode(TidRangePath);
     
    @@ -1215,9 +1216,9 @@ create_tidrangescan_path(PlannerInfo *root, RelOptInfo *rel,
     	pathnode->path.pathtarget = rel->reltarget;
     	pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
     														  required_outer);
    -	pathnode->path.parallel_aware = false;
    +	pathnode->path.parallel_aware = (parallel_workers > 0);
     	pathnode->path.parallel_safe = rel->consider_parallel;
    -	pathnode->path.parallel_workers = 0;
    +	pathnode->path.parallel_workers = parallel_workers;
     	pathnode->path.pathkeys = NIL;	/* always unordered */
     
     	pathnode->tidrangequals = tidrangequals;
    diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
    index 521043304a..9bede2d6e6 100644
    --- a/src/include/access/relscan.h
    +++ b/src/include/access/relscan.h
    @@ -81,6 +81,7 @@ typedef struct ParallelBlockTableScanDescData
     	BlockNumber phs_startblock; /* starting block number */
     	pg_atomic_uint64 phs_nallocated;	/* number of blocks allocated to
     										 * workers so far. */
    +	BlockNumber phs_numblock; /* max number of blocks to scan */
     }			ParallelBlockTableScanDescData;
     typedef struct ParallelBlockTableScanDescData *ParallelBlockTableScanDesc;
     
    diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
    index 8e583b45cd..2cffd813a5 100644
    --- a/src/include/access/tableam.h
    +++ b/src/include/access/tableam.h
    @@ -1175,6 +1175,16 @@ extern void table_parallelscan_initialize(Relation rel,
     extern TableScanDesc table_beginscan_parallel(Relation relation,
     											  ParallelTableScanDesc pscan);
     
    +/*
    + * Begin a parallel tidrange scan. `pscan` needs to have been initialized with
    + * table_parallelscan_initialize(), for the same relation. The initialization
    + * does not need to have happened in this backend.
    + *
    + * Caller must hold a suitable lock on the relation.
    + */
    +extern TableScanDesc table_beginscan_parallel_tidrange(Relation relation,
    +													   ParallelTableScanDesc pscan);
    +
     /*
      * Restart a parallel scan.  Call this in the leader process.  Caller is
      * responsible for making sure that all workers have finished the scan
    diff --git a/src/include/executor/nodeTidrangescan.h b/src/include/executor/nodeTidrangescan.h
    index 1cfc7a07be..977cb8eb6e 100644
    --- a/src/include/executor/nodeTidrangescan.h
    +++ b/src/include/executor/nodeTidrangescan.h
    @@ -14,6 +14,7 @@
     #ifndef NODETIDRANGESCAN_H
     #define NODETIDRANGESCAN_H
     
    +#include "access/parallel.h"
     #include "nodes/execnodes.h"
     
     extern TidRangeScanState *ExecInitTidRangeScan(TidRangeScan *node,
    @@ -21,4 +22,10 @@ extern TidRangeScanState *ExecInitTidRangeScan(TidRangeScan *node,
     extern void ExecEndTidRangeScan(TidRangeScanState *node);
     extern void ExecReScanTidRangeScan(TidRangeScanState *node);
     
    +/* parallel scan support */
    +extern void ExecTidRangeScanEstimate(TidRangeScanState *node, ParallelContext *pcxt);
    +extern void ExecTidRangeScanInitializeDSM(TidRangeScanState *node, ParallelContext *pcxt);
    +extern void ExecTidRangeScanReInitializeDSM(TidRangeScanState *node, ParallelContext *pcxt);
    +extern void ExecTidRangeScanInitializeWorker(TidRangeScanState *node, ParallelWorkerContext *pwcxt);
    +
     #endif							/* NODETIDRANGESCAN_H */
    diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
    index d927ac44a8..81eec34730 100644
    --- a/src/include/nodes/execnodes.h
    +++ b/src/include/nodes/execnodes.h
    @@ -1862,6 +1862,7 @@ typedef struct TidRangeScanState
     	ItemPointerData trss_mintid;
     	ItemPointerData trss_maxtid;
     	bool		trss_inScan;
    +	Size		pscan_len;		/* size of parallel tid range scan descriptor */
     } TidRangeScanState;
     
     /* ----------------
    diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
    index c5c4756b0f..d7683ec1c3 100644
    --- a/src/include/optimizer/pathnode.h
    +++ b/src/include/optimizer/pathnode.h
    @@ -66,7 +66,8 @@ extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
     extern TidRangePath *create_tidrangescan_path(PlannerInfo *root,
     											  RelOptInfo *rel,
     											  List *tidrangequals,
    -											  Relids required_outer);
    +											  Relids required_outer,
    +											  int parallel_workers);
     extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel,
     									  List *subpaths, List *partial_subpaths,
     									  List *pathkeys, Relids required_outer,
    diff --git a/src/test/regress/expected/tidrangescan.out b/src/test/regress/expected/tidrangescan.out
    index 721f3b94e0..7d48b430d3 100644
    --- a/src/test/regress/expected/tidrangescan.out
    +++ b/src/test/regress/expected/tidrangescan.out
    @@ -297,4 +297,110 @@ FETCH LAST c;
     
     COMMIT;
     DROP TABLE tidrangescan;
    +-- tests for parallel tidrangescans
    +SET parallel_setup_cost=0;
    +SET parallel_tuple_cost=0;
    +SET min_parallel_table_scan_size=0;
    +SET max_parallel_workers_per_gather=4;
    +CREATE TABLE parallel_tidrangescan(id integer, data text) with (fillfactor=10);
    +-- insert enough tuples such that each page gets 5 tuples with fillfactor = 10
    +INSERT INTO parallel_tidrangescan SELECT i,repeat('x', 100) FROM generate_series(1,200) AS s(i);
    +-- ensure there are 40 pages for parallel test
    +select min(ctid), max(ctid) from parallel_tidrangescan;
    +  min  |  max   
    +-------+--------
    + (0,1) | (39,5)
    +(1 row)
    +
    +-- parallel range scans with upper bound
    +explain (costs off)
    +select count(*) from parallel_tidrangescan where ctid<'(30,1)';
    +                             QUERY PLAN                             
    +--------------------------------------------------------------------
    + Finalize Aggregate
    +   ->  Gather
    +         Workers Planned: 4
    +         ->  Partial Aggregate
    +               ->  Parallel Tid Range Scan on parallel_tidrangescan
    +                     TID Cond: (ctid < '(30,1)'::tid)
    +(6 rows)
    +
    +select count(*) from parallel_tidrangescan where ctid<'(30,1)';
    + count 
    +-------
    +   150
    +(1 row)
    +
    +-- parallel range scans with lower bound
    +explain (costs off)
    +select count(*) from parallel_tidrangescan where ctid>'(10,0)';
    +                             QUERY PLAN                             
    +--------------------------------------------------------------------
    + Finalize Aggregate
    +   ->  Gather
    +         Workers Planned: 4
    +         ->  Partial Aggregate
    +               ->  Parallel Tid Range Scan on parallel_tidrangescan
    +                     TID Cond: (ctid > '(10,0)'::tid)
    +(6 rows)
    +
    +select count(*) from parallel_tidrangescan where ctid>'(10,0)';
    + count 
    +-------
    +   150
    +(1 row)
    +
    +-- parallel range scans with both bounds
    +explain (costs off)
    +select count(*) from parallel_tidrangescan where ctid>'(10,0)' and ctid<'(30,1)';
    +                                    QUERY PLAN                                     
    +-----------------------------------------------------------------------------------
    + Finalize Aggregate
    +   ->  Gather
    +         Workers Planned: 4
    +         ->  Partial Aggregate
    +               ->  Parallel Tid Range Scan on parallel_tidrangescan
    +                     TID Cond: ((ctid > '(10,0)'::tid) AND (ctid < '(30,1)'::tid))
    +(6 rows)
    +
    +select count(*) from parallel_tidrangescan where ctid>'(10,0)' and ctid<'(30,1)';
    + count 
    +-------
    +   100
    +(1 row)
    +
    +-- parallel rescans
    +EXPLAIN (COSTS OFF)
    +SELECT t.ctid,t2.c FROM parallel_tidrangescan t,
    +LATERAL (SELECT count(*) c FROM parallel_tidrangescan t2 WHERE t2.ctid <= t.ctid) t2
    +WHERE t.ctid < '(1,0)';
    +                           QUERY PLAN                           
    +----------------------------------------------------------------
    + Nested Loop
    +   ->  Gather
    +         Workers Planned: 4
    +         ->  Parallel Tid Range Scan on parallel_tidrangescan t
    +               TID Cond: (ctid < '(1,0)'::tid)
    +   ->  Aggregate
    +         ->  Tid Range Scan on parallel_tidrangescan t2
    +               TID Cond: (ctid <= t.ctid)
    +(8 rows)
    +
    +SELECT t.ctid,t2.c FROM parallel_tidrangescan t,
    +LATERAL (SELECT count(*) c FROM parallel_tidrangescan t2 WHERE t2.ctid <= t.ctid) t2
    +WHERE t.ctid < '(1,0)';
    + ctid  | c 
    +-------+---
    + (0,1) | 1
    + (0,2) | 2
    + (0,3) | 3
    + (0,4) | 4
    + (0,5) | 5
    +(5 rows)
    +
    +DROP TABLE parallel_tidrangescan;
    +RESET parallel_setup_cost;
    +RESET parallel_tuple_cost;
    +RESET min_parallel_table_scan_size;
    +RESET max_parallel_workers_per_gather;
     RESET enable_seqscan;
    diff --git a/src/test/regress/sql/tidrangescan.sql b/src/test/regress/sql/tidrangescan.sql
    index ac09ebb626..5b9bf8efc7 100644
    --- a/src/test/regress/sql/tidrangescan.sql
    +++ b/src/test/regress/sql/tidrangescan.sql
    @@ -98,4 +98,49 @@ COMMIT;
     
     DROP TABLE tidrangescan;
     
    +-- tests for parallel tidrangescans
    +SET parallel_setup_cost=0;
    +SET parallel_tuple_cost=0;
    +SET min_parallel_table_scan_size=0;
    +SET max_parallel_workers_per_gather=4;
    +
    +CREATE TABLE parallel_tidrangescan(id integer, data text) with (fillfactor=10);
    +
    +-- insert enough tuples such that each page gets 5 tuples with fillfactor = 10
    +INSERT INTO parallel_tidrangescan SELECT i,repeat('x', 100) FROM generate_series(1,200) AS s(i);
    +
    +-- ensure there are 40 pages for parallel test
    +select min(ctid), max(ctid) from parallel_tidrangescan;
    +
    +-- parallel range scans with upper bound
    +explain (costs off)
    +select count(*) from parallel_tidrangescan where ctid<'(30,1)';
    +select count(*) from parallel_tidrangescan where ctid<'(30,1)';
    +
    +-- parallel range scans with lower bound
    +explain (costs off)
    +select count(*) from parallel_tidrangescan where ctid>'(10,0)';
    +select count(*) from parallel_tidrangescan where ctid>'(10,0)';
    +
    +-- parallel range scans with both bounds
    +explain (costs off)
    +select count(*) from parallel_tidrangescan where ctid>'(10,0)' and ctid<'(30,1)';
    +select count(*) from parallel_tidrangescan where ctid>'(10,0)' and ctid<'(30,1)';
    +
    +-- parallel rescans
    +EXPLAIN (COSTS OFF)
    +SELECT t.ctid,t2.c FROM parallel_tidrangescan t,
    +LATERAL (SELECT count(*) c FROM parallel_tidrangescan t2 WHERE t2.ctid <= t.ctid) t2
    +WHERE t.ctid < '(1,0)';
    +
    +SELECT t.ctid,t2.c FROM parallel_tidrangescan t,
    +LATERAL (SELECT count(*) c FROM parallel_tidrangescan t2 WHERE t2.ctid <= t.ctid) t2
    +WHERE t.ctid < '(1,0)';
    +
    +DROP TABLE parallel_tidrangescan;
    +
    +RESET parallel_setup_cost;
    +RESET parallel_tuple_cost;
    +RESET min_parallel_table_scan_size;
    +RESET max_parallel_workers_per_gather;
     RESET enable_seqscan;
    -- 
    2.17.1
    
    
    ------=_Part_5426285_2131212966.1715722423182--