Compare commits

..

No commits in common. "890a73ba3ac0ea9ec2ad6a828ffe03ca12b11564" and "e6b41d5682d98c498827f1c17aa3b115c1dde961" have entirely different histories.

5 changed files with 81 additions and 377 deletions

View File

@ -4941,14 +4941,13 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
<listitem> <listitem>
<para> <para>
Enables or disables the query planner's use of partitionwise grouping Enables or disables the query planner's use of partitionwise grouping
or aggregation, which allows grouping or aggregation on partitioned or aggregation, which allows grouping or aggregation on a partitioned
tables to be performed separately for each partition. If the tables performed separately for each partition. If the <literal>GROUP
<literal>GROUP BY</literal> clause does not include the partition BY</literal> clause does not include the partition keys, only partial
keys, only partial aggregation can be performed on a per-partition aggregation can be performed on a per-partition basis, and
basis, and finalization must be performed later. Because finalization must be performed later. Because partitionwise grouping
partitionwise grouping or aggregation can use significantly more CPU or aggregation can use significantly more CPU time and memory during
time and memory during planning, the default is planning, the default is <literal>off</literal>.
<literal>off</literal>.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -1878,7 +1878,7 @@ ExecInitPruningContext(PartitionPruneContext *context,
foreach(lc, pruning_steps) foreach(lc, pruning_steps)
{ {
PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc); PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
ListCell *lc2 = list_head(step->exprs); ListCell *lc2;
int keyno; int keyno;
/* not needed for other step kinds */ /* not needed for other step kinds */
@ -1887,14 +1887,10 @@ ExecInitPruningContext(PartitionPruneContext *context,
Assert(list_length(step->exprs) <= partnatts); Assert(list_length(step->exprs) <= partnatts);
for (keyno = 0; keyno < partnatts; keyno++) keyno = 0;
foreach(lc2, step->exprs)
{ {
if (bms_is_member(keyno, step->nullkeys)) Expr *expr = (Expr *) lfirst(lc2);
continue;
if (lc2 != NULL)
{
Expr *expr = lfirst(lc2);
/* not needed for Consts */ /* not needed for Consts */
if (!IsA(expr, Const)) if (!IsA(expr, Const))
@ -1906,8 +1902,7 @@ ExecInitPruningContext(PartitionPruneContext *context,
context->exprstates[stateidx] = context->exprstates[stateidx] =
ExecInitExpr(expr, context->planstate); ExecInitExpr(expr, context->planstate);
} }
lc2 = lnext(step->exprs, lc2); keyno++;
}
} }
} }
} }

View File

@ -165,6 +165,7 @@ static List *get_steps_using_prefix(GeneratePruningStepsContext *context,
bool step_op_is_ne, bool step_op_is_ne,
Expr *step_lastexpr, Expr *step_lastexpr,
Oid step_lastcmpfn, Oid step_lastcmpfn,
int step_lastkeyno,
Bitmapset *step_nullkeys, Bitmapset *step_nullkeys,
List *prefix); List *prefix);
static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
@ -172,6 +173,7 @@ static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context
bool step_op_is_ne, bool step_op_is_ne,
Expr *step_lastexpr, Expr *step_lastexpr,
Oid step_lastcmpfn, Oid step_lastcmpfn,
int step_lastkeyno,
Bitmapset *step_nullkeys, Bitmapset *step_nullkeys,
List *prefix, List *prefix,
ListCell *start, ListCell *start,
@ -1409,6 +1411,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
pc->op_is_ne, pc->op_is_ne,
pc->expr, pc->expr,
pc->cmpfn, pc->cmpfn,
0,
NULL, NULL,
NIL); NIL);
opsteps = list_concat(opsteps, pc_steps); opsteps = list_concat(opsteps, pc_steps);
@ -1534,6 +1537,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
pc->op_is_ne, pc->op_is_ne,
pc->expr, pc->expr,
pc->cmpfn, pc->cmpfn,
pc->keyno,
NULL, NULL,
prefix); prefix);
opsteps = list_concat(opsteps, pc_steps); opsteps = list_concat(opsteps, pc_steps);
@ -1607,6 +1611,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
false, false,
pc->expr, pc->expr,
pc->cmpfn, pc->cmpfn,
pc->keyno,
nullkeys, nullkeys,
prefix); prefix);
opsteps = list_concat(opsteps, pc_steps); opsteps = list_concat(opsteps, pc_steps);
@ -2246,31 +2251,25 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context,
/* /*
* get_steps_using_prefix * get_steps_using_prefix
* Generate a list of PartitionPruneStepOps based on the given input. * Generate list of PartitionPruneStepOp steps each consisting of given
* opstrategy
* *
* 'step_lastexpr' and 'step_lastcmpfn' are the Expr and comparison function * To generate steps, step_lastexpr and step_lastcmpfn are appended to
* belonging to the final partition key that we have a clause for. 'prefix' * expressions and cmpfns, respectively, extracted from the clauses in
* is a list of PartClauseInfos for partition key numbers prior to the given * 'prefix'. Actually, since 'prefix' may contain multiple clauses for the
* 'step_lastexpr' and 'step_lastcmpfn'. 'prefix' may contain multiple * same partition key column, we must generate steps for various combinations
* PartClauseInfos belonging to a single partition key. We will generate a * of the clauses of different keys.
* PartitionPruneStepOp for each combination of the given PartClauseInfos
* using, at most, one PartClauseInfo per partition key.
* *
* For LIST and RANGE partitioned tables, callers must ensure that * For list/range partitioning, callers must ensure that step_nullkeys is
* step_nullkeys is NULL, and that prefix contains at least one clause for * NULL, and that prefix contains at least one clause for each of the
* each of the partition keys prior to the key that 'step_lastexpr' and * partition keys earlier than one specified in step_lastkeyno if it's
* 'step_lastcmpfn'belong to. * greater than zero. For hash partitioning, step_nullkeys is allowed to be
* non-NULL, but they must ensure that prefix contains at least one clause
* for each of the partition keys other than those specified in step_nullkeys
* and step_lastkeyno.
* *
* For HASH partitioned tables, callers must ensure that 'prefix' contains at * For both cases, callers must also ensure that clauses in prefix are sorted
* least one clause for each of the partition keys apart from the final key * in ascending order of their partition key numbers.
* (the expr and comparison function for the final key are in 'step_lastexpr'
* and 'step_lastcmpfn'). A bit set in step_nullkeys can substitute clauses
* in the 'prefix' list for any given key. If a bit is set in 'step_nullkeys'
* for a given key, then there must be no PartClauseInfo for that key in the
* 'prefix' list.
*
* For each of the above cases, callers must ensure that PartClauseInfos in
* 'prefix' are sorted in ascending order of keyno.
*/ */
static List * static List *
get_steps_using_prefix(GeneratePruningStepsContext *context, get_steps_using_prefix(GeneratePruningStepsContext *context,
@ -2278,17 +2277,14 @@ get_steps_using_prefix(GeneratePruningStepsContext *context,
bool step_op_is_ne, bool step_op_is_ne,
Expr *step_lastexpr, Expr *step_lastexpr,
Oid step_lastcmpfn, Oid step_lastcmpfn,
int step_lastkeyno,
Bitmapset *step_nullkeys, Bitmapset *step_nullkeys,
List *prefix) List *prefix)
{ {
/* step_nullkeys must be empty for RANGE and LIST partitioned tables */
Assert(step_nullkeys == NULL || Assert(step_nullkeys == NULL ||
context->rel->part_scheme->strategy == PARTITION_STRATEGY_HASH); context->rel->part_scheme->strategy == PARTITION_STRATEGY_HASH);
/* /* Quick exit if there are no values to prefix with. */
* No recursive processing is required when 'prefix' is an empty list. This
* occurs when there is only 1 partition key column.
*/
if (list_length(prefix) == 0) if (list_length(prefix) == 0)
{ {
PartitionPruneStep *step; PartitionPruneStep *step;
@ -2302,12 +2298,13 @@ get_steps_using_prefix(GeneratePruningStepsContext *context,
return list_make1(step); return list_make1(step);
} }
/* Recurse to generate steps for every combination of clauses. */ /* Recurse to generate steps for various combinations. */
return get_steps_using_prefix_recurse(context, return get_steps_using_prefix_recurse(context,
step_opstrategy, step_opstrategy,
step_op_is_ne, step_op_is_ne,
step_lastexpr, step_lastexpr,
step_lastcmpfn, step_lastcmpfn,
step_lastkeyno,
step_nullkeys, step_nullkeys,
prefix, prefix,
list_head(prefix), list_head(prefix),
@ -2316,17 +2313,13 @@ get_steps_using_prefix(GeneratePruningStepsContext *context,
/* /*
* get_steps_using_prefix_recurse * get_steps_using_prefix_recurse
* Generate and return a list of PartitionPruneStepOps using the 'prefix' * Recursively generate combinations of clauses for different partition
* list of PartClauseInfos starting at the 'start' cell. * keys and start generating steps upon reaching clauses for the greatest
* column that is less than the one for which we're currently generating
* steps (that is, step_lastkeyno)
* *
* When 'prefix' contains multiple PartClauseInfos for a single partition key * 'prefix' is the list of PartClauseInfos.
* we create a PartitionPruneStepOp for each combination of duplicated * 'start' is where we should start iterating for the current invocation.
* PartClauseInfos. The returned list will contain a PartitionPruneStepOp
* for each unique combination of input PartClauseInfos containing at most one
* PartClauseInfo per partition key.
*
* 'prefix' is the input list of PartClauseInfos sorted by keyno.
* 'start' marks the cell that searching the 'prefix' list should start from.
* 'step_exprs' and 'step_cmpfns' each contains the expressions and cmpfns * 'step_exprs' and 'step_cmpfns' each contains the expressions and cmpfns
* we've generated so far from the clauses for the previous part keys. * we've generated so far from the clauses for the previous part keys.
*/ */
@ -2336,6 +2329,7 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
bool step_op_is_ne, bool step_op_is_ne,
Expr *step_lastexpr, Expr *step_lastexpr,
Oid step_lastcmpfn, Oid step_lastcmpfn,
int step_lastkeyno,
Bitmapset *step_nullkeys, Bitmapset *step_nullkeys,
List *prefix, List *prefix,
ListCell *start, ListCell *start,
@ -2345,25 +2339,23 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
List *result = NIL; List *result = NIL;
ListCell *lc; ListCell *lc;
int cur_keyno; int cur_keyno;
int final_keyno;
/* Actually, recursion would be limited by PARTITION_MAX_KEYS. */ /* Actually, recursion would be limited by PARTITION_MAX_KEYS. */
check_stack_depth(); check_stack_depth();
/* Check if we need to recurse. */
Assert(start != NULL); Assert(start != NULL);
cur_keyno = ((PartClauseInfo *) lfirst(start))->keyno; cur_keyno = ((PartClauseInfo *) lfirst(start))->keyno;
final_keyno = ((PartClauseInfo *) llast(prefix))->keyno; if (cur_keyno < step_lastkeyno - 1)
/* Check if we need to recurse. */
if (cur_keyno < final_keyno)
{ {
PartClauseInfo *pc; PartClauseInfo *pc;
ListCell *next_start; ListCell *next_start;
/* /*
* Find the first PartClauseInfo belonging to the next partition key, the * For each clause with cur_keyno, add its expr and cmpfn to
* next recursive call must start iteration of the prefix list from that * step_exprs and step_cmpfns, respectively, and recurse after setting
* point. * next_start to the ListCell of the first clause for the next
* partition key.
*/ */
for_each_cell(lc, prefix, start) for_each_cell(lc, prefix, start)
{ {
@ -2372,15 +2364,8 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
if (pc->keyno > cur_keyno) if (pc->keyno > cur_keyno)
break; break;
} }
/* record where to start iterating in the next recursive call */
next_start = lc; next_start = lc;
/*
* For each PartClauseInfo with keyno set to cur_keyno, add its expr and
* cmpfn to step_exprs and step_cmpfns, respectively, and recurse using
* 'next_start' as the starting point in the 'prefix' list.
*/
for_each_cell(lc, prefix, start) for_each_cell(lc, prefix, start)
{ {
List *moresteps; List *moresteps;
@ -2400,7 +2385,6 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
} }
else else
{ {
/* check the 'prefix' list is sorted correctly */
Assert(pc->keyno > cur_keyno); Assert(pc->keyno > cur_keyno);
break; break;
} }
@ -2410,6 +2394,7 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
step_op_is_ne, step_op_is_ne,
step_lastexpr, step_lastexpr,
step_lastcmpfn, step_lastcmpfn,
step_lastkeyno,
step_nullkeys, step_nullkeys,
prefix, prefix,
next_start, next_start,
@ -2428,8 +2413,8 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
* each clause with cur_keyno, which is all clauses from here onward * each clause with cur_keyno, which is all clauses from here onward
* till the end of the list. Note that for hash partitioning, * till the end of the list. Note that for hash partitioning,
* step_nullkeys is allowed to be non-empty, in which case step_exprs * step_nullkeys is allowed to be non-empty, in which case step_exprs
* would only contain expressions for the partition keys that are not * would only contain expressions for the earlier partition keys that
* specified in step_nullkeys. * are not specified in step_nullkeys.
*/ */
Assert(list_length(step_exprs) == cur_keyno || Assert(list_length(step_exprs) == cur_keyno ||
!bms_is_empty(step_nullkeys)); !bms_is_empty(step_nullkeys));

View File

@ -1948,6 +1948,7 @@ explain (costs off) select * from hp where a = 1 and b = 'abcde' and
One-Time Filter: false One-Time Filter: false
(2 rows) (2 rows)
drop table hp;
-- --
-- Test runtime partition pruning -- Test runtime partition pruning
-- --
@ -2069,27 +2070,6 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
Filter: ((b >= $1) AND (b <= $2) AND (a < $0)) Filter: ((b >= $1) AND (b <= $2) AND (a < $0))
(10 rows) (10 rows)
--
-- Test runtime pruning with hash partitioned tables
--
-- recreate partitions dropped above
create table hp1 partition of hp for values with (modulus 4, remainder 1);
create table hp2 partition of hp for values with (modulus 4, remainder 2);
create table hp3 partition of hp for values with (modulus 4, remainder 3);
-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual
prepare hp_q1 (text) as
select * from hp where a is null and b = $1;
explain (costs off) execute hp_q1('xxx');
QUERY PLAN
--------------------------------------------
Append
Subplans Removed: 3
-> Seq Scan on hp2 hp_1
Filter: ((a IS NULL) AND (b = $1))
(4 rows)
deallocate hp_q1;
drop table hp;
-- Test a backwards Append scan -- Test a backwards Append scan
create table list_part (a int) partition by list (a); create table list_part (a int) partition by list (a);
create table list_part1 partition of list_part for values in (1); create table list_part1 partition of list_part for values in (1);
@ -3974,217 +3954,20 @@ explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b
Filter: ((a >= 1) AND (b >= 1) AND (d >= 0) AND (b = 2) AND (c = 2)) Filter: ((a >= 1) AND (b >= 1) AND (d >= 0) AND (b = 2) AND (c = 2))
(2 rows) (2 rows)
create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops);
create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 2, remainder 0);
create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 2, remainder 1);
-- Test that get_steps_using_prefix() handles non-NULL step_nullkeys
explain (costs off) select * from hp_prefix_test where a = 1 and b is null and c = 1 and d = 1;
QUERY PLAN
-------------------------------------------------------------
Seq Scan on hp_prefix_test_p1 hp_prefix_test
Filter: ((b IS NULL) AND (a = 1) AND (c = 1) AND (d = 1))
(2 rows)
drop table rp_prefix_test1; drop table rp_prefix_test1;
drop table rp_prefix_test2; drop table rp_prefix_test2;
drop table rp_prefix_test3; drop table rp_prefix_test3;
--
-- Test that get_steps_using_prefix() handles IS NULL clauses correctly
--
create table hp_prefix_test (a int, b int, c int, d int)
partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops);
-- create 8 partitions
select 'create table hp_prefix_test_p' || x::text || ' partition of hp_prefix_test for values with (modulus 8, remainder ' || x::text || ');'
from generate_Series(0,7) x;
?column?
------------------------------------------------------------------------------------------------------
create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0);
create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1);
create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2);
create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3);
create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4);
create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5);
create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6);
create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7);
(8 rows)
\gexec
create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0);
create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1);
create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2);
create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3);
create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4);
create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5);
create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6);
create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7);
-- insert 16 rows, one row for each test to perform.
insert into hp_prefix_test
select
case a when 0 then null else 1 end,
case b when 0 then null else 2 end,
case c when 0 then null else 3 end,
case d when 0 then null else 4 end
from
generate_series(0,1) a,
generate_series(0,1) b,
generate_Series(0,1) c,
generate_Series(0,1) d;
-- Ensure partition pruning works correctly for each combination of IS NULL
-- and equality quals. This may seem a little excessive, but there have been
-- a number of bugs in this area over the years. We make use of row only
-- output to reduce the size of the expected results.
\t on
select
'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' ||
string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos)
from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s)
group by g.s
order by g.s;
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4
\gexec
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null
Seq Scan on hp_prefix_test_p0 hp_prefix_test
Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d IS NULL))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null
Seq Scan on hp_prefix_test_p1 hp_prefix_test
Filter: ((b IS NULL) AND (c IS NULL) AND (d IS NULL) AND (a = 1))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null
Seq Scan on hp_prefix_test_p2 hp_prefix_test
Filter: ((a IS NULL) AND (c IS NULL) AND (d IS NULL) AND (b = 2))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null
Seq Scan on hp_prefix_test_p4 hp_prefix_test
Filter: ((c IS NULL) AND (d IS NULL) AND (a = 1) AND (b = 2))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null
Seq Scan on hp_prefix_test_p3 hp_prefix_test
Filter: ((a IS NULL) AND (b IS NULL) AND (d IS NULL) AND (c = 3))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null
Seq Scan on hp_prefix_test_p7 hp_prefix_test
Filter: ((b IS NULL) AND (d IS NULL) AND (a = 1) AND (c = 3))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null
Seq Scan on hp_prefix_test_p4 hp_prefix_test
Filter: ((a IS NULL) AND (d IS NULL) AND (b = 2) AND (c = 3))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null
Seq Scan on hp_prefix_test_p5 hp_prefix_test
Filter: ((d IS NULL) AND (a = 1) AND (b = 2) AND (c = 3))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4
Seq Scan on hp_prefix_test_p4 hp_prefix_test
Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d = 4))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4
Seq Scan on hp_prefix_test_p6 hp_prefix_test
Filter: ((b IS NULL) AND (c IS NULL) AND (a = 1) AND (d = 4))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4
Seq Scan on hp_prefix_test_p5 hp_prefix_test
Filter: ((a IS NULL) AND (c IS NULL) AND (b = 2) AND (d = 4))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4
Seq Scan on hp_prefix_test_p6 hp_prefix_test
Filter: ((c IS NULL) AND (a = 1) AND (b = 2) AND (d = 4))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4
Seq Scan on hp_prefix_test_p4 hp_prefix_test
Filter: ((a IS NULL) AND (b IS NULL) AND (c = 3) AND (d = 4))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4
Seq Scan on hp_prefix_test_p5 hp_prefix_test
Filter: ((b IS NULL) AND (a = 1) AND (c = 3) AND (d = 4))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4
Seq Scan on hp_prefix_test_p6 hp_prefix_test
Filter: ((a IS NULL) AND (b = 2) AND (c = 3) AND (d = 4))
explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4
Seq Scan on hp_prefix_test_p4 hp_prefix_test
Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4))
-- And ensure we get exactly 1 row from each. Again, all 16 possible combinations.
select
'select tableoid::regclass,* from hp_prefix_test where ' ||
string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos)
from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s)
group by g.s
order by g.s;
select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null
select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null
select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null
select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null
select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null
select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null
select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null
select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null
select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4
select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4
select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4
select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4
select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4
select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4
select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4
select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4
\gexec
select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null
hp_prefix_test_p0 | | | |
select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null
hp_prefix_test_p1 | 1 | | |
select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null
hp_prefix_test_p2 | | 2 | |
select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null
hp_prefix_test_p4 | 1 | 2 | |
select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null
hp_prefix_test_p3 | | | 3 |
select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null
hp_prefix_test_p7 | 1 | | 3 |
select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null
hp_prefix_test_p4 | | 2 | 3 |
select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null
hp_prefix_test_p5 | 1 | 2 | 3 |
select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4
hp_prefix_test_p4 | | | | 4
select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4
hp_prefix_test_p6 | 1 | | | 4
select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4
hp_prefix_test_p5 | | 2 | | 4
select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4
hp_prefix_test_p6 | 1 | 2 | | 4
select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4
hp_prefix_test_p4 | | | 3 | 4
select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4
hp_prefix_test_p5 | 1 | | 3 | 4
select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4
hp_prefix_test_p6 | | 2 | 3 | 4
select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4
hp_prefix_test_p4 | 1 | 2 | 3 | 4
\t off
drop table hp_prefix_test; drop table hp_prefix_test;
-- --
-- Check that gen_partprune_steps() detects self-contradiction from clauses -- Check that gen_partprune_steps() detects self-contradiction from clauses

View File

@ -384,6 +384,8 @@ drop table hp2;
explain (costs off) select * from hp where a = 1 and b = 'abcde' and explain (costs off) select * from hp where a = 1 and b = 'abcde' and
(c = 2 or c = 3); (c = 2 or c = 3);
drop table hp;
-- --
-- Test runtime partition pruning -- Test runtime partition pruning
-- --
@ -434,25 +436,6 @@ select a from ab where b between $1 and $2 and a < (select 3);
explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
--
-- Test runtime pruning with hash partitioned tables
--
-- recreate partitions dropped above
create table hp1 partition of hp for values with (modulus 4, remainder 1);
create table hp2 partition of hp for values with (modulus 4, remainder 2);
create table hp3 partition of hp for values with (modulus 4, remainder 3);
-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual
prepare hp_q1 (text) as
select * from hp where a is null and b = $1;
explain (costs off) execute hp_q1('xxx');
deallocate hp_q1;
drop table hp;
-- Test a backwards Append scan -- Test a backwards Append scan
create table list_part (a int) partition by list (a); create table list_part (a int) partition by list (a);
create table list_part1 partition of list_part for values in (1); create table list_part1 partition of list_part for values in (1);
@ -1149,57 +1132,16 @@ explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b
-- that the caller arranges clauses in that prefix in the required order) -- that the caller arranges clauses in that prefix in the required order)
explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b = 2 and c = 2 and d >= 0; explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b = 2 and c = 2 and d >= 0;
create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops);
create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 2, remainder 0);
create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 2, remainder 1);
-- Test that get_steps_using_prefix() handles non-NULL step_nullkeys
explain (costs off) select * from hp_prefix_test where a = 1 and b is null and c = 1 and d = 1;
drop table rp_prefix_test1; drop table rp_prefix_test1;
drop table rp_prefix_test2; drop table rp_prefix_test2;
drop table rp_prefix_test3; drop table rp_prefix_test3;
--
-- Test that get_steps_using_prefix() handles IS NULL clauses correctly
--
create table hp_prefix_test (a int, b int, c int, d int)
partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops);
-- create 8 partitions
select 'create table hp_prefix_test_p' || x::text || ' partition of hp_prefix_test for values with (modulus 8, remainder ' || x::text || ');'
from generate_Series(0,7) x;
\gexec
-- insert 16 rows, one row for each test to perform.
insert into hp_prefix_test
select
case a when 0 then null else 1 end,
case b when 0 then null else 2 end,
case c when 0 then null else 3 end,
case d when 0 then null else 4 end
from
generate_series(0,1) a,
generate_series(0,1) b,
generate_Series(0,1) c,
generate_Series(0,1) d;
-- Ensure partition pruning works correctly for each combination of IS NULL
-- and equality quals. This may seem a little excessive, but there have been
-- a number of bugs in this area over the years. We make use of row only
-- output to reduce the size of the expected results.
\t on
select
'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' ||
string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos)
from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s)
group by g.s
order by g.s;
\gexec
-- And ensure we get exactly 1 row from each. Again, all 16 possible combinations.
select
'select tableoid::regclass,* from hp_prefix_test where ' ||
string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos)
from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s)
group by g.s
order by g.s;
\gexec
\t off
drop table hp_prefix_test; drop table hp_prefix_test;
-- --