Skip to content

Commit 5935917

Browse files
committed
Allow executor startup pruning to prune all child nodes.
Previously, if the startup pruning logic proved that all child nodes of an Append or MergeAppend could be pruned, we still kept one, just to keep EXPLAIN from failing. The previous commit removed the ruleutils.c limitation that required this kluge, so drop it. That results in less-confusing EXPLAIN output, as per a complaint from Yuzuko Hosoya. David Rowley Discussion: https://postgr.es/m/[email protected]
1 parent 6ef77cf commit 5935917

File tree

5 files changed

+69
-104
lines changed

5 files changed

+69
-104
lines changed

src/backend/executor/nodeAppend.c

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ struct ParallelAppendState
7878
};
7979

8080
#define INVALID_SUBPLAN_INDEX -1
81-
#define NO_MATCHING_SUBPLANS -2
8281

8382
static TupleTableSlot *ExecAppend(PlanState *pstate);
8483
static bool choose_next_subplan_locally(AppendState *node);
@@ -141,23 +140,6 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
141140
validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
142141
list_length(node->appendplans));
143142

144-
/*
145-
* The case where no subplans survive pruning must be handled
146-
* specially. The problem here is that code in explain.c requires
147-
* an Append to have at least one subplan in order for it to
148-
* properly determine the Vars in that subplan's targetlist. We
149-
* sidestep this issue by just initializing the first subplan and
150-
* setting as_whichplan to NO_MATCHING_SUBPLANS to indicate that
151-
* we don't really need to scan any subnodes.
152-
*/
153-
if (bms_is_empty(validsubplans))
154-
{
155-
appendstate->as_whichplan = NO_MATCHING_SUBPLANS;
156-
157-
/* Mark the first as valid so that it's initialized below */
158-
validsubplans = bms_make_singleton(0);
159-
}
160-
161143
nplans = bms_num_members(validsubplans);
162144
}
163145
else
@@ -169,14 +151,12 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
169151
}
170152

171153
/*
172-
* If no runtime pruning is required, we can fill as_valid_subplans
173-
* immediately, preventing later calls to ExecFindMatchingSubPlans.
154+
* When no run-time pruning is required and there's at least one
155+
* subplan, we can fill as_valid_subplans immediately, preventing
156+
* later calls to ExecFindMatchingSubPlans.
174157
*/
175-
if (!prunestate->do_exec_prune)
176-
{
177-
Assert(nplans > 0);
158+
if (!prunestate->do_exec_prune && nplans > 0)
178159
appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
179-
}
180160
}
181161
else
182162
{
@@ -255,17 +235,17 @@ ExecAppend(PlanState *pstate)
255235

256236
if (node->as_whichplan < 0)
257237
{
238+
/* Nothing to do if there are no subplans */
239+
if (node->as_nplans == 0)
240+
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
241+
258242
/*
259243
* If no subplan has been chosen, we must choose one before
260244
* proceeding.
261245
*/
262246
if (node->as_whichplan == INVALID_SUBPLAN_INDEX &&
263247
!node->choose_next_subplan(node))
264248
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
265-
266-
/* Nothing to do if there are no matching subplans */
267-
else if (node->as_whichplan == NO_MATCHING_SUBPLANS)
268-
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
269249
}
270250

271251
for (;;)
@@ -460,7 +440,7 @@ choose_next_subplan_locally(AppendState *node)
460440
int nextplan;
461441

462442
/* We should never be called when there are no subplans */
463-
Assert(whichplan != NO_MATCHING_SUBPLANS);
443+
Assert(node->as_nplans > 0);
464444

465445
/*
466446
* If first call then have the bms member function choose the first valid
@@ -511,7 +491,7 @@ choose_next_subplan_for_leader(AppendState *node)
511491
Assert(ScanDirectionIsForward(node->ps.state->es_direction));
512492

513493
/* We should never be called when there are no subplans */
514-
Assert(node->as_whichplan != NO_MATCHING_SUBPLANS);
494+
Assert(node->as_nplans > 0);
515495

516496
LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE);
517497

@@ -592,7 +572,7 @@ choose_next_subplan_for_worker(AppendState *node)
592572
Assert(ScanDirectionIsForward(node->ps.state->es_direction));
593573

594574
/* We should never be called when there are no subplans */
595-
Assert(node->as_whichplan != NO_MATCHING_SUBPLANS);
575+
Assert(node->as_nplans > 0);
596576

597577
LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE);
598578

src/backend/executor/nodeMergeAppend.c

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
8080
mergestate->ps.plan = (Plan *) node;
8181
mergestate->ps.state = estate;
8282
mergestate->ps.ExecProcNode = ExecMergeAppend;
83-
mergestate->ms_noopscan = false;
8483

8584
/* If run-time partition pruning is enabled, then set that up now */
8685
if (node->part_prune_info != NULL)
@@ -101,23 +100,6 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
101100
validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
102101
list_length(node->mergeplans));
103102

104-
/*
105-
* The case where no subplans survive pruning must be handled
106-
* specially. The problem here is that code in explain.c requires
107-
* a MergeAppend to have at least one subplan in order for it to
108-
* properly determine the Vars in that subplan's targetlist. We
109-
* sidestep this issue by just initializing the first subplan and
110-
* setting ms_noopscan to true to indicate that we don't really
111-
* need to scan any subnodes.
112-
*/
113-
if (bms_is_empty(validsubplans))
114-
{
115-
mergestate->ms_noopscan = true;
116-
117-
/* Mark the first as valid so that it's initialized below */
118-
validsubplans = bms_make_singleton(0);
119-
}
120-
121103
nplans = bms_num_members(validsubplans);
122104
}
123105
else
@@ -129,14 +111,12 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
129111
}
130112

131113
/*
132-
* If no runtime pruning is required, we can fill ms_valid_subplans
133-
* immediately, preventing later calls to ExecFindMatchingSubPlans.
114+
* When no run-time pruning is required and there's at least one
115+
* subplan, we can fill as_valid_subplans immediately, preventing
116+
* later calls to ExecFindMatchingSubPlans.
134117
*/
135-
if (!prunestate->do_exec_prune)
136-
{
137-
Assert(nplans > 0);
118+
if (!prunestate->do_exec_prune && nplans > 0)
138119
mergestate->ms_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
139-
}
140120
}
141121
else
142122
{
@@ -240,7 +220,7 @@ ExecMergeAppend(PlanState *pstate)
240220
if (!node->ms_initialized)
241221
{
242222
/* Nothing to do if all subplans were pruned */
243-
if (node->ms_noopscan)
223+
if (node->ms_nplans == 0)
244224
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
245225

246226
/*

src/include/nodes/execnodes.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,8 +1234,6 @@ struct AppendState
12341234
* slots current output tuple of each subplan
12351235
* heap heap of active tuples
12361236
* initialized true if we have fetched first tuple from each subplan
1237-
* noopscan true if partition pruning proved that none of the
1238-
* mergeplans can contain a record to satisfy this query.
12391237
* prune_state details required to allow partitions to be
12401238
* eliminated from the scan, or NULL if not possible.
12411239
* valid_subplans for runtime pruning, valid mergeplans indexes to
@@ -1252,7 +1250,6 @@ typedef struct MergeAppendState
12521250
TupleTableSlot **ms_slots; /* array of length ms_nplans */
12531251
struct binaryheap *ms_heap; /* binary heap of slot indices */
12541252
bool ms_initialized; /* are subplans started? */
1255-
bool ms_noopscan;
12561253
struct PartitionPruneState *ms_prune_state;
12571254
Bitmapset *ms_valid_subplans;
12581255
} MergeAppendState;

src/test/regress/expected/partition_prune.out

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,20 +2005,17 @@ select explain_parallel_append('execute ab_q5 (2, 3, 3)');
20052005
(19 rows)
20062006

20072007
-- Try some params whose values do not belong to any partition.
2008-
-- We'll still get a single subplan in this case, but it should not be scanned.
20092008
select explain_parallel_append('execute ab_q5 (33, 44, 55)');
2010-
explain_parallel_append
2011-
-------------------------------------------------------------------------------
2009+
explain_parallel_append
2010+
-----------------------------------------------------------
20122011
Finalize Aggregate (actual rows=N loops=N)
20132012
-> Gather (actual rows=N loops=N)
20142013
Workers Planned: 2
20152014
Workers Launched: N
20162015
-> Partial Aggregate (actual rows=N loops=N)
20172016
-> Parallel Append (actual rows=N loops=N)
2018-
Subplans Removed: 8
2019-
-> Parallel Seq Scan on ab_a1_b1 ab_1 (never executed)
2020-
Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
2021-
(9 rows)
2017+
Subplans Removed: 9
2018+
(7 rows)
20222019

20232020
-- Test Parallel Append with PARAM_EXEC Params
20242021
select explain_parallel_append('select count(*) from ab where (a = (select 1) or a = (select 3)) and b = 2');
@@ -2854,16 +2851,13 @@ explain (analyze, costs off, summary off, timing off) execute q1 (2,2);
28542851
Filter: (b = ANY (ARRAY[$1, $2]))
28552852
(4 rows)
28562853

2857-
-- Try with no matching partitions. One subplan should remain in this case,
2858-
-- but it shouldn't be executed.
2854+
-- Try with no matching partitions.
28592855
explain (analyze, costs off, summary off, timing off) execute q1 (0,0);
2860-
QUERY PLAN
2861-
------------------------------------------------------
2856+
QUERY PLAN
2857+
--------------------------------
28622858
Append (actual rows=0 loops=1)
2863-
Subplans Removed: 1
2864-
-> Seq Scan on listp_1_1 listp_1 (never executed)
2865-
Filter: (b = ANY (ARRAY[$1, $2]))
2866-
(4 rows)
2859+
Subplans Removed: 2
2860+
(2 rows)
28672861

28682862
deallocate q1;
28692863
-- Test more complex cases where a not-equal condition further eliminates partitions.
@@ -2879,15 +2873,12 @@ explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,0);
28792873
(4 rows)
28802874

28812875
-- Both partitions allowed by IN clause, then both excluded again by <> clauses.
2882-
-- One subplan will remain in this case, but it should not be executed.
28832876
explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1);
2884-
QUERY PLAN
2885-
-------------------------------------------------------------------------
2877+
QUERY PLAN
2878+
--------------------------------
28862879
Append (actual rows=0 loops=1)
2887-
Subplans Removed: 1
2888-
-> Seq Scan on listp_1_1 listp_1 (never executed)
2889-
Filter: ((b = ANY (ARRAY[$1, $2])) AND ($3 <> b) AND ($4 <> b))
2890-
(4 rows)
2880+
Subplans Removed: 2
2881+
(2 rows)
28912882

28922883
-- Ensure Params that evaluate to NULL properly prune away all partitions
28932884
explain (analyze, costs off, summary off, timing off)
@@ -2971,13 +2962,11 @@ select * from stable_qual_pruning
29712962
explain (analyze, costs off, summary off, timing off)
29722963
select * from stable_qual_pruning
29732964
where a = any(array['2010-02-01', '2020-01-01']::timestamptz[]);
2974-
QUERY PLAN
2975-
---------------------------------------------------------------------------------------------------------------------------
2965+
QUERY PLAN
2966+
--------------------------------
29762967
Append (actual rows=0 loops=1)
2977-
Subplans Removed: 2
2978-
-> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (never executed)
2979-
Filter: (a = ANY ('{"Mon Feb 01 00:00:00 2010 PST","Wed Jan 01 00:00:00 2020 PST"}'::timestamp with time zone[]))
2980-
(4 rows)
2968+
Subplans Removed: 3
2969+
(2 rows)
29812970

29822971
explain (analyze, costs off, summary off, timing off)
29832972
select * from stable_qual_pruning
@@ -3159,21 +3148,34 @@ execute mt_q1(25);
31593148

31603149
-- Ensure MergeAppend behaves correctly when no subplans match
31613150
explain (analyze, costs off, summary off, timing off) execute mt_q1(35);
3162-
QUERY PLAN
3163-
----------------------------------------------------------------------------------
3151+
QUERY PLAN
3152+
--------------------------------------
31643153
Merge Append (actual rows=0 loops=1)
31653154
Sort Key: ma_test.b
3166-
Subplans Removed: 2
3167-
-> Index Scan using ma_test_p1_b_idx on ma_test_p1 ma_test_1 (never executed)
3168-
Filter: ((a >= $1) AND ((a % 10) = 5))
3169-
(5 rows)
3155+
Subplans Removed: 3
3156+
(3 rows)
31703157

31713158
execute mt_q1(35);
31723159
a
31733160
---
31743161
(0 rows)
31753162

31763163
deallocate mt_q1;
3164+
set plan_cache_mode = force_generic_plan;
3165+
prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1;
3166+
-- Ensure output list looks sane when the MergeAppend has no subplans.
3167+
explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35);
3168+
QUERY PLAN
3169+
--------------------------------------------
3170+
Limit (actual rows=0 loops=1)
3171+
Output: ma_test.a, ma_test.b
3172+
-> Merge Append (actual rows=0 loops=1)
3173+
Sort Key: ma_test.b
3174+
Subplans Removed: 3
3175+
(5 rows)
3176+
3177+
deallocate mt_q2;
3178+
reset plan_cache_mode;
31773179
-- ensure initplan params properly prune partitions
31783180
explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b;
31793181
QUERY PLAN
@@ -3591,19 +3593,18 @@ from (
35913593
) s(a, b, c)
35923594
where s.a = $1 and s.b = $2 and s.c = (select 1);
35933595
explain (costs off) execute q (1, 1);
3594-
QUERY PLAN
3595-
---------------------------------------------------------------
3596+
QUERY PLAN
3597+
----------------------------------------------------
35963598
Append
35973599
InitPlan 1 (returns $0)
35983600
-> Result
3599-
Subplans Removed: 1
36003601
-> Seq Scan on p1 p
3601-
Filter: ((a = $1) AND (b = $2) AND (c = $0))
3602+
Filter: ((a = 1) AND (b = 1) AND (c = $0))
36023603
-> Seq Scan on q111 q1
3603-
Filter: ((a = $1) AND (b = $2) AND (c = $0))
3604+
Filter: ((a = 1) AND (b = 1) AND (c = $0))
36043605
-> Result
3605-
One-Time Filter: ((1 = $1) AND (1 = $2) AND (1 = $0))
3606-
(10 rows)
3606+
One-Time Filter: (1 = $0)
3607+
(9 rows)
36073608

36083609
execute q (1, 1);
36093610
a | b | c

src/test/regress/sql/partition_prune.sql

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,6 @@ select explain_parallel_append('execute ab_q5 (1, 1, 1)');
477477
select explain_parallel_append('execute ab_q5 (2, 3, 3)');
478478

479479
-- Try some params whose values do not belong to any partition.
480-
-- We'll still get a single subplan in this case, but it should not be scanned.
481480
select explain_parallel_append('execute ab_q5 (33, 44, 55)');
482481

483482
-- Test Parallel Append with PARAM_EXEC Params
@@ -702,8 +701,7 @@ explain (analyze, costs off, summary off, timing off) execute q1 (1,1);
702701

703702
explain (analyze, costs off, summary off, timing off) execute q1 (2,2);
704703

705-
-- Try with no matching partitions. One subplan should remain in this case,
706-
-- but it shouldn't be executed.
704+
-- Try with no matching partitions.
707705
explain (analyze, costs off, summary off, timing off) execute q1 (0,0);
708706

709707
deallocate q1;
@@ -715,7 +713,6 @@ prepare q1 (int,int,int,int) as select * from listp where b in($1,$2) and $3 <>
715713
explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,0);
716714

717715
-- Both partitions allowed by IN clause, then both excluded again by <> clauses.
718-
-- One subplan will remain in this case, but it should not be executed.
719716
explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1);
720717

721718
-- Ensure Params that evaluate to NULL properly prune away all partitions
@@ -841,6 +838,16 @@ execute mt_q1(35);
841838

842839
deallocate mt_q1;
843840

841+
set plan_cache_mode = force_generic_plan;
842+
843+
prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1;
844+
845+
-- Ensure output list looks sane when the MergeAppend has no subplans.
846+
explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35);
847+
848+
deallocate mt_q2;
849+
reset plan_cache_mode;
850+
844851
-- ensure initplan params properly prune partitions
845852
explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b;
846853

0 commit comments

Comments
 (0)