mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Fix some issues with step generation in partition pruning.
In the case of range partitioning, get_steps_using_prefix() assumes that the passed-in prefix list contains at least one clause for each of the partition keys earlier than one specified in the passed-in step_lastkeyno, but the caller (ie, gen_prune_steps_from_opexps()) didn't take it into account, which led to a server crash or incorrect results when the list contained no clauses for such partition keys, as reported in bug #16500 and #16501 from Kobayashi Hisanori. Update the caller to call that function only when the list created there contains at least one clause for each of the earlier partition keys in the case of range partitioning. While at it, fix some other issues: * The list to pass to get_steps_using_prefix() is allowed to contain multiple clauses for the same partition key, as described in the comment for that function, but that function actually assumed that the list contained just a single clause for each of middle partition keys, which led to an assertion failure when the list contained multiple clauses for such partition keys. Update that function to match the comment. * In the case of hash partitioning, partition keys are allowed to be NULL, in which case the list to pass to get_steps_using_prefix() contains no clauses for NULL partition keys, but that function treats that case as like the case of range partitioning, which led to the assertion failure. Update the assertion test to take into account NULL partition keys in the case of hash partitioning. * Fix a typo in a comment in get_steps_using_prefix_recurse(). * gen_partprune_steps() failed to detect self-contradiction from strict-qual clauses and an IS NULL clause for the same partition key in some cases, producing incorrect partition-pruning steps, which led to incorrect results of partition pruning, but didn't cause any user-visible problems fortunately, as the self-contradiction is detected later in the query planning. Update that function to detect the self-contradiction. Per bug #16500 and #16501 from Kobayashi Hisanori. Patch by me, initial diagnosis for the reported issue and review by Dmitry Dolgov. Back-patch to v11, where partition pruning was introduced. Discussion: https://postgr.es/m/16500-d1613f2a78e1e090%40postgresql.org Discussion: https://postgr.es/m/16501-5234a9a0394f6754%40postgresql.org
This commit is contained in:
@ -3923,3 +3923,95 @@ explain (costs off) update listp1 set a = 1 where a = 2;
|
||||
reset constraint_exclusion;
|
||||
reset enable_partition_pruning;
|
||||
drop table listp;
|
||||
--
|
||||
-- Check that gen_prune_steps_from_opexps() works well for various cases of
|
||||
-- clauses for different partition keys
|
||||
--
|
||||
create table rp_prefix_test1 (a int, b varchar) partition by range(a, b);
|
||||
create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b');
|
||||
create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (2, 'a') to (2, 'b');
|
||||
-- Don't call get_steps_using_prefix() with the last partition key b plus
|
||||
-- an empty prefix
|
||||
explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a';
|
||||
QUERY PLAN
|
||||
--------------------------------------------------
|
||||
Seq Scan on rp_prefix_test1_p1
|
||||
Filter: ((a <= 1) AND ((b)::text = 'a'::text))
|
||||
(2 rows)
|
||||
|
||||
create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c);
|
||||
create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10);
|
||||
create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (2, 2, 0) to (2, 2, 10);
|
||||
-- Don't call get_steps_using_prefix() with the last partition key c plus
|
||||
-- an invalid prefix (ie, b = 1)
|
||||
explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c >= 0;
|
||||
QUERY PLAN
|
||||
-----------------------------------------------
|
||||
Seq Scan on rp_prefix_test2_p1
|
||||
Filter: ((a <= 1) AND (c >= 0) AND (b = 1))
|
||||
(2 rows)
|
||||
|
||||
create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d);
|
||||
create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10);
|
||||
create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (2, 2, 2, 0) to (2, 2, 2, 10);
|
||||
-- Test that get_steps_using_prefix() handles a prefix that contains multiple
|
||||
-- clauses for the partition key b (ie, b >= 1 and b >= 2)
|
||||
explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b >= 2 and c >= 2 and d >= 0;
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------
|
||||
Seq Scan on rp_prefix_test3_p2
|
||||
Filter: ((a >= 1) AND (b >= 1) AND (b >= 2) AND (c >= 2) AND (d >= 0))
|
||||
(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
|
||||
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;
|
||||
drop table hp_prefix_test;
|
||||
--
|
||||
-- Check that gen_partprune_steps() detects self-contradiction from clauses
|
||||
-- regardless of the order of the clauses (Here we use a custom operator to
|
||||
-- prevent the equivclass.c machinery from reordering the clauses)
|
||||
--
|
||||
create operator === (
|
||||
leftarg = int4,
|
||||
rightarg = int4,
|
||||
procedure = int4eq,
|
||||
commutator = ===,
|
||||
hashes
|
||||
);
|
||||
create operator class part_test_int4_ops2
|
||||
for type int4
|
||||
using hash as
|
||||
operator 1 ===,
|
||||
function 2 part_hashint4_noop(int4, int8);
|
||||
create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2);
|
||||
create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0);
|
||||
create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1);
|
||||
explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1;
|
||||
QUERY PLAN
|
||||
--------------------------
|
||||
Result
|
||||
One-Time Filter: false
|
||||
(2 rows)
|
||||
|
||||
explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null;
|
||||
QUERY PLAN
|
||||
--------------------------
|
||||
Result
|
||||
One-Time Filter: false
|
||||
(2 rows)
|
||||
|
||||
drop table hp_contradict_test;
|
||||
drop operator class part_test_int4_ops2 using hash;
|
||||
drop operator ===(int4, int4);
|
||||
|
@ -1111,3 +1111,74 @@ reset constraint_exclusion;
|
||||
reset enable_partition_pruning;
|
||||
|
||||
drop table listp;
|
||||
|
||||
--
|
||||
-- Check that gen_prune_steps_from_opexps() works well for various cases of
|
||||
-- clauses for different partition keys
|
||||
--
|
||||
|
||||
create table rp_prefix_test1 (a int, b varchar) partition by range(a, b);
|
||||
create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b');
|
||||
create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (2, 'a') to (2, 'b');
|
||||
|
||||
-- Don't call get_steps_using_prefix() with the last partition key b plus
|
||||
-- an empty prefix
|
||||
explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a';
|
||||
|
||||
create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c);
|
||||
create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10);
|
||||
create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (2, 2, 0) to (2, 2, 10);
|
||||
|
||||
-- Don't call get_steps_using_prefix() with the last partition key c plus
|
||||
-- an invalid prefix (ie, b = 1)
|
||||
explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c >= 0;
|
||||
|
||||
create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d);
|
||||
create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10);
|
||||
create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (2, 2, 2, 0) to (2, 2, 2, 10);
|
||||
|
||||
-- Test that get_steps_using_prefix() handles a prefix that contains multiple
|
||||
-- clauses for the partition key b (ie, b >= 1 and b >= 2)
|
||||
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;
|
||||
drop table hp_prefix_test;
|
||||
|
||||
--
|
||||
-- Check that gen_partprune_steps() detects self-contradiction from clauses
|
||||
-- regardless of the order of the clauses (Here we use a custom operator to
|
||||
-- prevent the equivclass.c machinery from reordering the clauses)
|
||||
--
|
||||
|
||||
create operator === (
|
||||
leftarg = int4,
|
||||
rightarg = int4,
|
||||
procedure = int4eq,
|
||||
commutator = ===,
|
||||
hashes
|
||||
);
|
||||
create operator class part_test_int4_ops2
|
||||
for type int4
|
||||
using hash as
|
||||
operator 1 ===,
|
||||
function 2 part_hashint4_noop(int4, int8);
|
||||
|
||||
create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2);
|
||||
create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0);
|
||||
create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1);
|
||||
|
||||
explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1;
|
||||
explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null;
|
||||
|
||||
drop table hp_contradict_test;
|
||||
drop operator class part_test_int4_ops2 using hash;
|
||||
drop operator ===(int4, int4);
|
||||
|
Reference in New Issue
Block a user