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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy