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/001001d4f44b$2a2cca50$7e865ef0$@lab.ntt.co.jp
This commit is contained in:
Tom Lane 2019-12-11 17:05:30 -05:00
parent 6ef77cf46e
commit 5935917ce5
5 changed files with 69 additions and 104 deletions

View File

@ -78,7 +78,6 @@ struct ParallelAppendState
};
#define INVALID_SUBPLAN_INDEX -1
#define NO_MATCHING_SUBPLANS -2
static TupleTableSlot *ExecAppend(PlanState *pstate);
static bool choose_next_subplan_locally(AppendState *node);
@ -141,23 +140,6 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
list_length(node->appendplans));
/*
* The case where no subplans survive pruning must be handled
* specially. The problem here is that code in explain.c requires
* an Append to have at least one subplan in order for it to
* properly determine the Vars in that subplan's targetlist. We
* sidestep this issue by just initializing the first subplan and
* setting as_whichplan to NO_MATCHING_SUBPLANS to indicate that
* we don't really need to scan any subnodes.
*/
if (bms_is_empty(validsubplans))
{
appendstate->as_whichplan = NO_MATCHING_SUBPLANS;
/* Mark the first as valid so that it's initialized below */
validsubplans = bms_make_singleton(0);
}
nplans = bms_num_members(validsubplans);
}
else
@ -169,14 +151,12 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
}
/*
* If no runtime pruning is required, we can fill as_valid_subplans
* immediately, preventing later calls to ExecFindMatchingSubPlans.
* When no run-time pruning is required and there's at least one
* subplan, we can fill as_valid_subplans immediately, preventing
* later calls to ExecFindMatchingSubPlans.
*/
if (!prunestate->do_exec_prune)
{
Assert(nplans > 0);
if (!prunestate->do_exec_prune && nplans > 0)
appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
}
}
else
{
@ -255,6 +235,10 @@ ExecAppend(PlanState *pstate)
if (node->as_whichplan < 0)
{
/* Nothing to do if there are no subplans */
if (node->as_nplans == 0)
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
/*
* If no subplan has been chosen, we must choose one before
* proceeding.
@ -262,10 +246,6 @@ ExecAppend(PlanState *pstate)
if (node->as_whichplan == INVALID_SUBPLAN_INDEX &&
!node->choose_next_subplan(node))
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
/* Nothing to do if there are no matching subplans */
else if (node->as_whichplan == NO_MATCHING_SUBPLANS)
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
}
for (;;)
@ -460,7 +440,7 @@ choose_next_subplan_locally(AppendState *node)
int nextplan;
/* We should never be called when there are no subplans */
Assert(whichplan != NO_MATCHING_SUBPLANS);
Assert(node->as_nplans > 0);
/*
* If first call then have the bms member function choose the first valid
@ -511,7 +491,7 @@ choose_next_subplan_for_leader(AppendState *node)
Assert(ScanDirectionIsForward(node->ps.state->es_direction));
/* We should never be called when there are no subplans */
Assert(node->as_whichplan != NO_MATCHING_SUBPLANS);
Assert(node->as_nplans > 0);
LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE);
@ -592,7 +572,7 @@ choose_next_subplan_for_worker(AppendState *node)
Assert(ScanDirectionIsForward(node->ps.state->es_direction));
/* We should never be called when there are no subplans */
Assert(node->as_whichplan != NO_MATCHING_SUBPLANS);
Assert(node->as_nplans > 0);
LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE);

View File

@ -80,7 +80,6 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
mergestate->ps.plan = (Plan *) node;
mergestate->ps.state = estate;
mergestate->ps.ExecProcNode = ExecMergeAppend;
mergestate->ms_noopscan = false;
/* If run-time partition pruning is enabled, then set that up now */
if (node->part_prune_info != NULL)
@ -101,23 +100,6 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
list_length(node->mergeplans));
/*
* The case where no subplans survive pruning must be handled
* specially. The problem here is that code in explain.c requires
* a MergeAppend to have at least one subplan in order for it to
* properly determine the Vars in that subplan's targetlist. We
* sidestep this issue by just initializing the first subplan and
* setting ms_noopscan to true to indicate that we don't really
* need to scan any subnodes.
*/
if (bms_is_empty(validsubplans))
{
mergestate->ms_noopscan = true;
/* Mark the first as valid so that it's initialized below */
validsubplans = bms_make_singleton(0);
}
nplans = bms_num_members(validsubplans);
}
else
@ -129,14 +111,12 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
}
/*
* If no runtime pruning is required, we can fill ms_valid_subplans
* immediately, preventing later calls to ExecFindMatchingSubPlans.
* When no run-time pruning is required and there's at least one
* subplan, we can fill as_valid_subplans immediately, preventing
* later calls to ExecFindMatchingSubPlans.
*/
if (!prunestate->do_exec_prune)
{
Assert(nplans > 0);
if (!prunestate->do_exec_prune && nplans > 0)
mergestate->ms_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
}
}
else
{
@ -240,7 +220,7 @@ ExecMergeAppend(PlanState *pstate)
if (!node->ms_initialized)
{
/* Nothing to do if all subplans were pruned */
if (node->ms_noopscan)
if (node->ms_nplans == 0)
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
/*

View File

@ -1234,8 +1234,6 @@ struct AppendState
* slots current output tuple of each subplan
* heap heap of active tuples
* initialized true if we have fetched first tuple from each subplan
* noopscan true if partition pruning proved that none of the
* mergeplans can contain a record to satisfy this query.
* prune_state details required to allow partitions to be
* eliminated from the scan, or NULL if not possible.
* valid_subplans for runtime pruning, valid mergeplans indexes to
@ -1252,7 +1250,6 @@ typedef struct MergeAppendState
TupleTableSlot **ms_slots; /* array of length ms_nplans */
struct binaryheap *ms_heap; /* binary heap of slot indices */
bool ms_initialized; /* are subplans started? */
bool ms_noopscan;
struct PartitionPruneState *ms_prune_state;
Bitmapset *ms_valid_subplans;
} MergeAppendState;

View File

@ -2005,20 +2005,17 @@ select explain_parallel_append('execute ab_q5 (2, 3, 3)');
(19 rows)
-- Try some params whose values do not belong to any partition.
-- We'll still get a single subplan in this case, but it should not be scanned.
select explain_parallel_append('execute ab_q5 (33, 44, 55)');
explain_parallel_append
-------------------------------------------------------------------------------
explain_parallel_append
-----------------------------------------------------------
Finalize Aggregate (actual rows=N loops=N)
-> Gather (actual rows=N loops=N)
Workers Planned: 2
Workers Launched: N
-> Partial Aggregate (actual rows=N loops=N)
-> Parallel Append (actual rows=N loops=N)
Subplans Removed: 8
-> Parallel Seq Scan on ab_a1_b1 ab_1 (never executed)
Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
(9 rows)
Subplans Removed: 9
(7 rows)
-- Test Parallel Append with PARAM_EXEC Params
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);
Filter: (b = ANY (ARRAY[$1, $2]))
(4 rows)
-- Try with no matching partitions. One subplan should remain in this case,
-- but it shouldn't be executed.
-- Try with no matching partitions.
explain (analyze, costs off, summary off, timing off) execute q1 (0,0);
QUERY PLAN
------------------------------------------------------
QUERY PLAN
--------------------------------
Append (actual rows=0 loops=1)
Subplans Removed: 1
-> Seq Scan on listp_1_1 listp_1 (never executed)
Filter: (b = ANY (ARRAY[$1, $2]))
(4 rows)
Subplans Removed: 2
(2 rows)
deallocate q1;
-- 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);
(4 rows)
-- Both partitions allowed by IN clause, then both excluded again by <> clauses.
-- One subplan will remain in this case, but it should not be executed.
explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1);
QUERY PLAN
-------------------------------------------------------------------------
QUERY PLAN
--------------------------------
Append (actual rows=0 loops=1)
Subplans Removed: 1
-> Seq Scan on listp_1_1 listp_1 (never executed)
Filter: ((b = ANY (ARRAY[$1, $2])) AND ($3 <> b) AND ($4 <> b))
(4 rows)
Subplans Removed: 2
(2 rows)
-- Ensure Params that evaluate to NULL properly prune away all partitions
explain (analyze, costs off, summary off, timing off)
@ -2971,13 +2962,11 @@ select * from stable_qual_pruning
explain (analyze, costs off, summary off, timing off)
select * from stable_qual_pruning
where a = any(array['2010-02-01', '2020-01-01']::timestamptz[]);
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
QUERY PLAN
--------------------------------
Append (actual rows=0 loops=1)
Subplans Removed: 2
-> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (never executed)
Filter: (a = ANY ('{"Mon Feb 01 00:00:00 2010 PST","Wed Jan 01 00:00:00 2020 PST"}'::timestamp with time zone[]))
(4 rows)
Subplans Removed: 3
(2 rows)
explain (analyze, costs off, summary off, timing off)
select * from stable_qual_pruning
@ -3159,14 +3148,12 @@ execute mt_q1(25);
-- Ensure MergeAppend behaves correctly when no subplans match
explain (analyze, costs off, summary off, timing off) execute mt_q1(35);
QUERY PLAN
----------------------------------------------------------------------------------
QUERY PLAN
--------------------------------------
Merge Append (actual rows=0 loops=1)
Sort Key: ma_test.b
Subplans Removed: 2
-> Index Scan using ma_test_p1_b_idx on ma_test_p1 ma_test_1 (never executed)
Filter: ((a >= $1) AND ((a % 10) = 5))
(5 rows)
Subplans Removed: 3
(3 rows)
execute mt_q1(35);
a
@ -3174,6 +3161,21 @@ execute mt_q1(35);
(0 rows)
deallocate mt_q1;
set plan_cache_mode = force_generic_plan;
prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1;
-- Ensure output list looks sane when the MergeAppend has no subplans.
explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35);
QUERY PLAN
--------------------------------------------
Limit (actual rows=0 loops=1)
Output: ma_test.a, ma_test.b
-> Merge Append (actual rows=0 loops=1)
Sort Key: ma_test.b
Subplans Removed: 3
(5 rows)
deallocate mt_q2;
reset plan_cache_mode;
-- ensure initplan params properly prune partitions
explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b;
QUERY PLAN
@ -3591,19 +3593,18 @@ from (
) s(a, b, c)
where s.a = $1 and s.b = $2 and s.c = (select 1);
explain (costs off) execute q (1, 1);
QUERY PLAN
---------------------------------------------------------------
QUERY PLAN
----------------------------------------------------
Append
InitPlan 1 (returns $0)
-> Result
Subplans Removed: 1
-> Seq Scan on p1 p
Filter: ((a = $1) AND (b = $2) AND (c = $0))
Filter: ((a = 1) AND (b = 1) AND (c = $0))
-> Seq Scan on q111 q1
Filter: ((a = $1) AND (b = $2) AND (c = $0))
Filter: ((a = 1) AND (b = 1) AND (c = $0))
-> Result
One-Time Filter: ((1 = $1) AND (1 = $2) AND (1 = $0))
(10 rows)
One-Time Filter: (1 = $0)
(9 rows)
execute q (1, 1);
a | b | c

View File

@ -477,7 +477,6 @@ select explain_parallel_append('execute ab_q5 (1, 1, 1)');
select explain_parallel_append('execute ab_q5 (2, 3, 3)');
-- Try some params whose values do not belong to any partition.
-- We'll still get a single subplan in this case, but it should not be scanned.
select explain_parallel_append('execute ab_q5 (33, 44, 55)');
-- Test Parallel Append with PARAM_EXEC Params
@ -702,8 +701,7 @@ explain (analyze, costs off, summary off, timing off) execute q1 (1,1);
explain (analyze, costs off, summary off, timing off) execute q1 (2,2);
-- Try with no matching partitions. One subplan should remain in this case,
-- but it shouldn't be executed.
-- Try with no matching partitions.
explain (analyze, costs off, summary off, timing off) execute q1 (0,0);
deallocate q1;
@ -715,7 +713,6 @@ prepare q1 (int,int,int,int) as select * from listp where b in($1,$2) and $3 <>
explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,0);
-- Both partitions allowed by IN clause, then both excluded again by <> clauses.
-- One subplan will remain in this case, but it should not be executed.
explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1);
-- Ensure Params that evaluate to NULL properly prune away all partitions
@ -841,6 +838,16 @@ execute mt_q1(35);
deallocate mt_q1;
set plan_cache_mode = force_generic_plan;
prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1;
-- Ensure output list looks sane when the MergeAppend has no subplans.
explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35);
deallocate mt_q2;
reset plan_cache_mode;
-- ensure initplan params properly prune partitions
explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b;