Skip to content

Commit 11be868

Browse files
committed
Initial commit
This commit can show EXPLAIN of partial plans including temporary tables. Just for demo. Don't even try to execute that!
1 parent 82ed67a commit 11be868

File tree

6 files changed

+370
-0
lines changed

6 files changed

+370
-0
lines changed

contrib/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ SUBDIRS = \
4545
spi \
4646
tablefunc \
4747
tcn \
48+
tempscan \
4849
test_decoding \
4950
tsm_system_rows \
5051
tsm_system_time \

contrib/tempscan/Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# contrib/tempscan/Makefile
2+
3+
MODULE_big = tempscan
4+
OBJS = \
5+
$(WIN32RES) \
6+
nodeCustomTempScan.o \
7+
8+
EXTENSION = tempscan
9+
PGFILEDESC = "tempscan - Parallel Scan of Temporary Table"
10+
11+
REGRESS = basic
12+
13+
ifdef USE_PGXS
14+
PG_CONFIG = pg_config
15+
PGXS := $(shell $(PG_CONFIG) --pgxs)
16+
include $(PGXS)
17+
else
18+
subdir = contrib/tempscan
19+
top_builddir = ../..
20+
include $(top_builddir)/src/Makefile.global
21+
include $(top_srcdir)/contrib/contrib-global.mk
22+
endif

contrib/tempscan/expected/basic.out

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--
2+
-- Copyright (c) 2017-2024, Postgres Professional
3+
--
4+
-- Set of basic regression tests on scanning of a temporary table in parallel
5+
--
6+
-- Load the library. It should be load dynamically
7+
LOAD 'tempscan';
8+
-- Force usage of parallel workers
9+
SET max_parallel_workers_per_gather = 3;
10+
SET parallel_setup_cost = 0.0001;
11+
SET parallel_tuple_cost = 0.0001;
12+
-- Don't need big tables
13+
SET min_parallel_table_scan_size = 0;
14+
SET min_parallel_index_scan_size = 0;
15+
CREATE TABLE parallel_test (x int);
16+
INSERT INTO parallel_test (x) SELECT x FROM generate_series(1,100) AS x;
17+
CREATE TEMP TABLE parallel_test_tmp AS (SELECT * FROM parallel_test);
18+
VACUUM ANALYZE parallel_test, parallel_test_tmp;
19+
SET tempscan.enable = 'on';
20+
EXPLAIN (COSTS OFF)
21+
SELECT count(*) FROM parallel_test;
22+
QUERY PLAN
23+
------------------------------------------------------
24+
Finalize Aggregate
25+
-> Gather
26+
Workers Planned: 1
27+
-> Partial Aggregate
28+
-> Parallel Seq Scan on parallel_test
29+
(5 rows)
30+
31+
-- Should also utilise parallel workers like scanning of a plain table
32+
EXPLAIN (COSTS OFF)
33+
SELECT count(*) FROM parallel_test_tmp;
34+
QUERY PLAN
35+
----------------------------------------------------------
36+
Aggregate
37+
-> Gather
38+
Workers Planned: 1
39+
-> Custom Scan (nodeCustomTempScan)
40+
-> Parallel Seq Scan on parallel_test_tmp
41+
(5 rows)
42+
43+
RESET tempscan.enable;
44+
DROP TABLE parallel_test, parallel_test_tmp;

contrib/tempscan/nodeCustomTempScan.c

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/* -------------------------------------------------------------------------
2+
*
3+
* nodeCustomTempScan.c
4+
* Implements strategy which allows to build and execute partial paths for
5+
* a query which contains tempoorary table scans.
6+
*
7+
* Copyright (c) 2017-2024, Postgres Professional
8+
*
9+
* IDENTIFICATION
10+
* contrib/tempscan/nodeCustomTempScan.c
11+
*
12+
* -------------------------------------------------------------------------
13+
*/
14+
#include "postgres.h"
15+
16+
#include "nodes/extensible.h"
17+
#include "optimizer/cost.h"
18+
#include "optimizer/pathnode.h"
19+
#include "optimizer/paths.h"
20+
#include "utils/guc.h"
21+
#include "utils/lsyscache.h"
22+
#include "utils/rel.h"
23+
24+
PG_MODULE_MAGIC;
25+
26+
#define MODULENAME "tempscan"
27+
#define NODENAME "nodeCustomTempScan"
28+
29+
static Plan *create_partial_tempscan_plan(PlannerInfo *root,
30+
RelOptInfo *rel,
31+
CustomPath *best_path,
32+
List *tlist,
33+
List *scan_clauses,
34+
List *custom_plans);
35+
static Node *create_tempscan_state(CustomScan *cscan);
36+
static void BeginTempScan(CustomScanState *node, EState *estate, int eflags);
37+
static TupleTableSlot *ExecTempScan(CustomScanState *node);
38+
static void EndTempScan(CustomScanState *node);
39+
static void ReScanTempScan(CustomScanState *node);
40+
41+
static CustomPathMethods path_methods =
42+
{
43+
.CustomName = NODENAME,
44+
.PlanCustomPath = create_partial_tempscan_plan,
45+
.ReparameterizeCustomPathByChild = NULL
46+
};
47+
48+
static CustomScanMethods plan_methods =
49+
{
50+
.CustomName = NODENAME,
51+
.CreateCustomScanState = create_tempscan_state
52+
};
53+
54+
static CustomExecMethods exec_methods =
55+
{
56+
.CustomName = NODENAME,
57+
58+
.BeginCustomScan = BeginTempScan,
59+
.ExecCustomScan = ExecTempScan,
60+
.EndCustomScan = EndTempScan,
61+
.ReScanCustomScan = ReScanTempScan,
62+
.MarkPosCustomScan = NULL,
63+
.RestrPosCustomScan = NULL,
64+
.EstimateDSMCustomScan = NULL,
65+
.InitializeDSMCustomScan = NULL,
66+
.ReInitializeDSMCustomScan = NULL,
67+
.InitializeWorkerCustomScan = NULL,
68+
.ShutdownCustomScan = NULL,
69+
.ExplainCustomScan = NULL
70+
};
71+
72+
static set_rel_pathlist_hook_type set_rel_pathlist_hook_next = NULL;
73+
static bool tempscan_enable = false;
74+
75+
void _PG_init(void);
76+
77+
/*
78+
* The input path shouldn't be a part of the relation pathlist.
79+
*/
80+
static CustomPath *
81+
create_partial_tempscan_path(PlannerInfo *root, RelOptInfo *rel,
82+
Path *path)
83+
{
84+
CustomPath *cpath;
85+
Path *pathnode;
86+
87+
cpath = makeNode(CustomPath);
88+
pathnode = &cpath->path;
89+
90+
pathnode->pathtype = T_CustomScan;
91+
pathnode->parent = rel;
92+
pathnode->pathtarget = rel->reltarget;
93+
pathnode->rows = rel->rows;
94+
95+
/* XXX: Just for now */
96+
pathnode->param_info = NULL;
97+
98+
pathnode->parallel_safe = true;
99+
pathnode->parallel_aware = false;
100+
pathnode->parallel_workers = path->parallel_workers;
101+
102+
/* DEBUGGING purposes only */
103+
pathnode->startup_cost = path->startup_cost / disable_cost;
104+
pathnode->total_cost = path->total_cost / disable_cost;
105+
106+
cpath->custom_paths = list_make1(path);
107+
cpath->custom_private = NIL;
108+
cpath->custom_restrictinfo = NIL;
109+
cpath->methods = &path_methods;
110+
111+
return cpath;
112+
}
113+
114+
static Plan *
115+
create_partial_tempscan_plan(PlannerInfo *root, RelOptInfo *rel,
116+
CustomPath *best_path, List *tlist,
117+
List *scan_clauses, List *custom_plans)
118+
{
119+
CustomScan *cscan = makeNode(CustomScan);
120+
121+
Assert(list_length(custom_plans) == 1);
122+
Assert(best_path->path.parallel_safe = true &&
123+
best_path->path.parallel_workers > 0);
124+
125+
126+
cscan->scan.plan.targetlist = cscan->custom_scan_tlist = tlist;
127+
cscan->scan.scanrelid = 0;
128+
cscan->custom_exprs = NIL;
129+
cscan->custom_plans = custom_plans;
130+
cscan->methods = &plan_methods;
131+
cscan->flags = best_path->flags;
132+
cscan->custom_private = best_path->custom_private;
133+
134+
return &cscan->scan.plan;
135+
}
136+
137+
static Node *
138+
create_tempscan_state(CustomScan *cscan)
139+
{
140+
CustomScanState *cstate = makeNode(CustomScanState);
141+
142+
cstate->methods = &exec_methods;
143+
144+
return (Node *) cstate;
145+
}
146+
147+
static void
148+
BeginTempScan(CustomScanState *node, EState *estate, int eflags)
149+
{
150+
CustomScan *cscan = (CustomScan *) node->ss.ps.plan;
151+
Plan *subplan;
152+
PlanState *pstate;
153+
154+
Assert(list_length(cscan->custom_plans) == 1);
155+
156+
subplan = (Plan *) linitial(cscan->custom_plans);
157+
pstate = ExecInitNode(subplan, estate, eflags);
158+
node->custom_ps = lappend(node->custom_ps, (void *) pstate);
159+
}
160+
161+
static TupleTableSlot *
162+
ExecTempScan(CustomScanState *node)
163+
{
164+
Assert(list_length(node->custom_ps) == 1);
165+
166+
return ExecProcNode((PlanState *) linitial(node->custom_ps));
167+
}
168+
169+
static void
170+
EndTempScan(CustomScanState *node)
171+
{
172+
ExecClearTuple(node->ss.ss_ScanTupleSlot);
173+
ExecEndNode((PlanState *) linitial(node->custom_ps));
174+
}
175+
176+
static void
177+
ReScanTempScan(CustomScanState *node)
178+
{
179+
PlanState *child;
180+
181+
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
182+
183+
child = (PlanState *) linitial(node->custom_ps);
184+
185+
if (node->ss.ps.chgParam != NULL)
186+
UpdateChangedParamSet(child, node->ss.ps.chgParam);
187+
188+
ExecReScan(child);
189+
}
190+
191+
/*
192+
* Try to add partial paths to the scan of a temporary table.
193+
*
194+
* In contrast to the hook on a JOIN paths creation, here we already at the end
195+
* of paths creation procedure, right before insertion of a gather node.
196+
* So, we can discover pathlist and choose any base path we can and want to use
197+
* in parallel scan.
198+
*
199+
* TODO: add inner strategy for temp table scan (parallel_workers == 0,
200+
* parallel_safe == true). Right now it looks a bit more difficult to implement.
201+
*/
202+
static void
203+
try_partial_tempscan(PlannerInfo *root, RelOptInfo *rel, Index rti,
204+
RangeTblEntry *rte)
205+
{
206+
int parallel_workers;
207+
Path *path;
208+
209+
/*
210+
* Some extension intercept this hook earlier. Allow it to do a work
211+
* before us.
212+
*/
213+
if (set_rel_pathlist_hook_next)
214+
(*set_rel_pathlist_hook_next)(root, rel, rti, rte);
215+
216+
if (rel->consider_parallel)
217+
return;
218+
219+
if (rte->rtekind != RTE_RELATION ||
220+
get_rel_persistence(rte->relid) != RELPERSISTENCE_TEMP)
221+
return;
222+
223+
parallel_workers = compute_parallel_worker(rel, rel->pages, -1,
224+
max_parallel_workers_per_gather);
225+
226+
/* If any limit was set to zero, the user doesn't want a parallel scan. */
227+
if (parallel_workers <= 0)
228+
return;
229+
230+
/* HACK */
231+
rel->consider_parallel = true;
232+
233+
path = create_seqscan_path(root, rel, NULL, parallel_workers);
234+
if (path)
235+
{
236+
/* Add an unordered partial path based on a parallel sequential scan. */
237+
add_partial_path(rel, (Path *)
238+
create_partial_tempscan_path(root, rel, path));
239+
}
240+
241+
if (!bms_equal(rel->relids, root->all_query_rels))
242+
rel->consider_parallel = false;
243+
Assert(IsA(linitial(rel->partial_pathlist), CustomPath));
244+
}
245+
246+
void
247+
_PG_init(void)
248+
{
249+
DefineCustomBoolVariable("tempscan.enable",
250+
"Enable feature of the parallel temporary table scan.",
251+
"Right now no any other purpose except debugging",
252+
&tempscan_enable,
253+
false,
254+
PGC_SUSET,
255+
0,
256+
NULL,
257+
NULL,
258+
NULL
259+
);
260+
261+
set_rel_pathlist_hook_next = set_rel_pathlist_hook;
262+
set_rel_pathlist_hook = try_partial_tempscan;
263+
264+
MarkGUCPrefixReserved(MODULENAME);
265+
}

contrib/tempscan/sql/basic.sql

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--
2+
-- Copyright (c) 2017-2024, Postgres Professional
3+
--
4+
-- Set of basic regression tests on scanning of a temporary table in parallel
5+
--
6+
7+
-- Load the library. It should be load dynamically
8+
LOAD 'tempscan';
9+
10+
-- Force usage of parallel workers
11+
SET max_parallel_workers_per_gather = 3;
12+
SET parallel_setup_cost = 0.0001;
13+
SET parallel_tuple_cost = 0.0001;
14+
15+
-- Don't need big tables
16+
SET min_parallel_table_scan_size = 0;
17+
SET min_parallel_index_scan_size = 0;
18+
19+
CREATE TABLE parallel_test (x int);
20+
INSERT INTO parallel_test (x) SELECT x FROM generate_series(1,100) AS x;
21+
CREATE TEMP TABLE parallel_test_tmp AS (SELECT * FROM parallel_test);
22+
VACUUM ANALYZE parallel_test, parallel_test_tmp;
23+
24+
SET tempscan.enable = 'on';
25+
EXPLAIN (COSTS OFF)
26+
SELECT count(*) FROM parallel_test;
27+
28+
-- Should also utilise parallel workers like scanning of a plain table
29+
EXPLAIN (COSTS OFF)
30+
SELECT count(*) FROM parallel_test_tmp;
31+
32+
RESET tempscan.enable;
33+
DROP TABLE parallel_test, parallel_test_tmp;

contrib/tempscan/tempscan.control

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# tempscan
2+
comment = 'Parallel Temp Table Scan'
3+
default_version = '0.1'
4+
module_pathname = '$libdir/tempscan'
5+
relocatable = true

0 commit comments

Comments
 (0)