diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 7179b22a05..ae76f7267e 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -167,7 +167,6 @@ static List *get_steps_using_prefix(GeneratePruningStepsContext *context, bool step_op_is_ne, Expr *step_lastexpr, Oid step_lastcmpfn, - int step_lastkeyno, Bitmapset *step_nullkeys, List *prefix); static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, @@ -175,7 +174,6 @@ static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context bool step_op_is_ne, Expr *step_lastexpr, Oid step_lastcmpfn, - int step_lastkeyno, Bitmapset *step_nullkeys, List *prefix, ListCell *start, @@ -1531,7 +1529,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context, pc->op_is_ne, pc->expr, pc->cmpfn, - 0, NULL, NIL); opsteps = list_concat(opsteps, pc_steps); @@ -1657,7 +1654,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context, pc->op_is_ne, pc->expr, pc->cmpfn, - pc->keyno, NULL, prefix); opsteps = list_concat(opsteps, pc_steps); @@ -1731,7 +1727,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context, false, pc->expr, pc->cmpfn, - pc->keyno, nullkeys, prefix); opsteps = list_concat(opsteps, pc_steps); @@ -2350,25 +2345,31 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context, /* * get_steps_using_prefix - * Generate list of PartitionPruneStepOp steps each consisting of given - * opstrategy + * Generate a list of PartitionPruneStepOps based on the given input. * - * To generate steps, step_lastexpr and step_lastcmpfn are appended to - * expressions and cmpfns, respectively, extracted from the clauses in - * 'prefix'. Actually, since 'prefix' may contain multiple clauses for the - * same partition key column, we must generate steps for various combinations - * of the clauses of different keys. + * 'step_lastexpr' and 'step_lastcmpfn' are the Expr and comparison function + * belonging to the final partition key that we have a clause for. 'prefix' + * is a list of PartClauseInfos for partition key numbers prior to the given + * 'step_lastexpr' and 'step_lastcmpfn'. 'prefix' may contain multiple + * PartClauseInfos belonging to a single partition key. We will generate a + * PartitionPruneStepOp for each combination of the given PartClauseInfos + * using, at most, one PartClauseInfo per partition key. * - * For list/range partitioning, callers must ensure that step_nullkeys is - * NULL, and that prefix contains at least one clause for each of the - * partition keys earlier than one specified in step_lastkeyno if it's - * 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 LIST and RANGE partitioned tables, callers must ensure that + * step_nullkeys is NULL, and that prefix contains at least one clause for + * each of the partition keys prior to the key that 'step_lastexpr' and + * 'step_lastcmpfn'belong to. * - * For both cases, callers must also ensure that clauses in prefix are sorted - * in ascending order of their partition key numbers. + * For HASH partitioned tables, callers must ensure that 'prefix' contains at + * least one clause for each of the partition keys apart from the final key + * (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 * get_steps_using_prefix(GeneratePruningStepsContext *context, @@ -2376,14 +2377,17 @@ get_steps_using_prefix(GeneratePruningStepsContext *context, bool step_op_is_ne, Expr *step_lastexpr, Oid step_lastcmpfn, - int step_lastkeyno, Bitmapset *step_nullkeys, List *prefix) { + /* step_nullkeys must be empty for RANGE and LIST partitioned tables */ Assert(step_nullkeys == NULL || 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 (prefix == NIL) { PartitionPruneStep *step; @@ -2397,13 +2401,12 @@ get_steps_using_prefix(GeneratePruningStepsContext *context, return list_make1(step); } - /* Recurse to generate steps for various combinations. */ + /* Recurse to generate steps for every combination of clauses. */ return get_steps_using_prefix_recurse(context, step_opstrategy, step_op_is_ne, step_lastexpr, step_lastcmpfn, - step_lastkeyno, step_nullkeys, prefix, list_head(prefix), @@ -2412,13 +2415,17 @@ get_steps_using_prefix(GeneratePruningStepsContext *context, /* * get_steps_using_prefix_recurse - * Recursively generate combinations of clauses for different partition - * 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) + * Generate and return a list of PartitionPruneStepOps using the 'prefix' + * list of PartClauseInfos starting at the 'start' cell. * - * 'prefix' is the list of PartClauseInfos. - * 'start' is where we should start iterating for the current invocation. + * When 'prefix' contains multiple PartClauseInfos for a single partition key + * we create a PartitionPruneStepOp for each combination of duplicated + * 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 * we've generated so far from the clauses for the previous part keys. */ @@ -2428,7 +2435,6 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, bool step_op_is_ne, Expr *step_lastexpr, Oid step_lastcmpfn, - int step_lastkeyno, Bitmapset *step_nullkeys, List *prefix, ListCell *start, @@ -2438,23 +2444,25 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, List *result = NIL; ListCell *lc; int cur_keyno; + int final_keyno; /* Actually, recursion would be limited by PARTITION_MAX_KEYS. */ check_stack_depth(); - /* Check if we need to recurse. */ Assert(start != NULL); cur_keyno = ((PartClauseInfo *) lfirst(start))->keyno; - if (cur_keyno < step_lastkeyno - 1) + final_keyno = ((PartClauseInfo *) llast(prefix))->keyno; + + /* Check if we need to recurse. */ + if (cur_keyno < final_keyno) { PartClauseInfo *pc; ListCell *next_start; /* - * For each clause with cur_keyno, add its expr and cmpfn to - * step_exprs and step_cmpfns, respectively, and recurse after setting - * next_start to the ListCell of the first clause for the next - * partition key. + * Find the first PartClauseInfo belonging to the next partition key, + * the next recursive call must start iteration of the prefix list + * from that point. */ for_each_cell(lc, prefix, start) { @@ -2463,8 +2471,15 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, if (pc->keyno > cur_keyno) break; } + + /* record where to start iterating in the next recursive call */ 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) { List *moresteps; @@ -2484,6 +2499,7 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, } else { + /* check the 'prefix' list is sorted correctly */ Assert(pc->keyno > cur_keyno); break; } @@ -2493,7 +2509,6 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, step_op_is_ne, step_lastexpr, step_lastcmpfn, - step_lastkeyno, step_nullkeys, prefix, next_start, @@ -2512,8 +2527,8 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, * each clause with cur_keyno, which is all clauses from here onward * till the end of the list. Note that for hash partitioning, * step_nullkeys is allowed to be non-empty, in which case step_exprs - * would only contain expressions for the earlier partition keys that - * are not specified in step_nullkeys. + * would only contain expressions for the partition keys that are not + * specified in step_nullkeys. */ Assert(list_length(step_exprs) == cur_keyno || !bms_is_empty(step_nullkeys)); diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 2abf759385..6452a3b5f5 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -4016,20 +4016,217 @@ 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)) (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_test2; 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; -- -- Check that gen_partprune_steps() detects self-contradiction from clauses diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index d1c60b8fe9..caf50458be 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -1182,16 +1182,57 @@ 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) 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_test2; 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; --