diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index 5ff986ac7d3..8b12a24cd5e 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -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); diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 18d13377dc3..e6896eff742 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -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); /* diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index ea93b769e1d..0c2a77aaf8d 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -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; diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 57680f1d292..6dc50e0aa4e 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -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 diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 41f0b6f1102..e00984edd45 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -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;