From: Martin Hansson Date: October 9 2009 9:30am Subject: bzr commit into mysql-5.1-bugteam branch (martin.hansson:3156) Bug#42846 List-Archive: http://lists.mysql.com/commits/86310 X-Bug: 42846 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============2058581674==" --===============2058581674== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///data0/martin/bzr/bug42846/5.1bt/ based on revid:mattias.jonsson@stripped 3156 Martin Hansson 2009-10-09 Bug#42846: wrong result returned for range scan when using covering index When two range predicates were combined under an OR predicate, the algorithm tried to merge overlapping ranges into one. But the case when a range overlapped several other ranges was not handled. This lead to 1) ranges overlapping, which gave repeated results and 2) a range that overlapped several other ranges was cut off. Fixed by 1) Making sure that a range got an upper bound equal to the next range with a greater minimum. 2) Removing a continue statement @ mysql-test/r/group_min_max.result Bug#42846: Changed query plans @ mysql-test/r/range.result Bug#42846: Test result. @ mysql-test/t/range.test Bug#42846: Test case. @ sql/opt_range.cc Bug#42846: The fix. Part1: Previously, both endpoints from key2 were copied, which is not safe. Since ranges are processed in ascending order of minimum endpoints, it is safe to copy the minimum endpoint from key2 but not the maximum. The maximum may only be copied if there is no other range or the other range's minimum is greater than key2's maximum. modified: mysql-test/r/group_min_max.result mysql-test/r/range.result mysql-test/t/range.test sql/opt_range.cc === modified file 'mysql-test/r/group_min_max.result' --- a/mysql-test/r/group_min_max.result 2009-08-30 07:03:37 +0000 +++ b/mysql-test/r/group_min_max.result 2009-10-09 09:30:40 +0000 @@ -876,10 +876,10 @@ id select_type table type possible_keys 1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by explain select a1,a2,b, max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using where; Using index for group-by +1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using where; Using index for group-by +1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b111') and (c <= 'g112') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by @@ -924,7 +924,7 @@ id select_type table type possible_keys 1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by explain select a1,a2,b, max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 range NULL idx_t2_1 146 NULL # Using where; Using index for group-by +1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by explain select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by === modified file 'mysql-test/r/range.result' --- a/mysql-test/r/range.result 2008-03-27 02:18:46 +0000 +++ b/mysql-test/r/range.result 2009-10-09 09:30:40 +0000 @@ -1219,3 +1219,182 @@ explain select * from t2 where a=1000 an id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 ref a a 5 const 502 Using where drop table t1, t2; +CREATE TABLE t1( a INT, b INT, KEY( a, b ) ); +CREATE TABLE t2( a INT, b INT, KEY( a, b ) ); +CREATE TABLE t3( a INT, b INT, KEY( a, b ) ); +INSERT INTO t1( a, b ) +VALUES (0, 1), (1, 2), (1, 4), (2, 3), (5, 0), (9, 7); +INSERT INTO t2( a, b ) +VALUES ( 1, 1), ( 2, 1), ( 3, 1), ( 4, 1), ( 5, 1), +( 6, 1), ( 7, 1), ( 8, 1), ( 9, 1), (10, 1), +(11, 1), (12, 1), (13, 1), (14, 1), (15, 1), +(16, 1), (17, 1), (18, 1), (19, 1), (20, 1); +INSERT INTO t2 SELECT a, 2 FROM t2 WHERE b = 1; +INSERT INTO t2 SELECT a, 3 FROM t2 WHERE b = 1; +INSERT INTO t2 SELECT -1, -1 FROM t2; +INSERT INTO t2 SELECT -1, -1 FROM t2; +INSERT INTO t2 SELECT -1, -1 FROM t2; +INSERT INTO t3 +VALUES (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), +(6, 0), (7, 0), (8, 0), (9, 0), (10, 0); +INSERT INTO t3 SELECT * FROM t3 WHERE a = 10; +INSERT INTO t3 SELECT * FROM t3 WHERE a = 10; +SELECT * FROM t1 WHERE +3 <= a AND a < 5 OR +5 < a AND b = 3 OR +3 <= a; +a b +5 0 +9 7 +EXPLAIN +SELECT * FROM t1 WHERE +3 <= a AND a < 5 OR +5 < a AND b = 3 OR +3 <= a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 3 Using where; Using index +SELECT * FROM t1 WHERE +3 <= a AND a < 5 OR +5 <= a AND b = 3 OR +3 <= a; +a b +5 0 +9 7 +EXPLAIN +SELECT * FROM t1 WHERE +3 <= a AND a < 5 OR +5 <= a AND b = 3 OR +3 <= a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 4 Using where; Using index +SELECT * FROM t1 WHERE +3 <= a AND a <= 5 OR +5 <= a AND b = 3 OR +3 <= a; +a b +5 0 +9 7 +EXPLAIN +SELECT * FROM t1 WHERE +3 <= a AND a <= 5 OR +5 <= a AND b = 3 OR +3 <= a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 3 Using where; Using index +SELECT * FROM t1 WHERE +3 <= a AND a <= 5 OR +3 <= a; +a b +5 0 +9 7 +EXPLAIN +SELECT * FROM t1 WHERE +3 <= a AND a <= 5 OR +3 <= a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 3 Using where; Using index +SELECT * FROM t2 WHERE +5 <= a AND a < 10 AND b = 1 OR +15 <= a AND a < 20 AND b = 3 +OR +1 <= a AND b = 1; +a b +1 1 +2 1 +3 1 +4 1 +5 1 +6 1 +7 1 +8 1 +9 1 +10 1 +11 1 +12 1 +13 1 +14 1 +15 1 +15 3 +16 1 +16 3 +17 1 +17 3 +18 1 +18 3 +19 1 +19 3 +20 1 +EXPLAIN +SELECT * FROM t2 WHERE +5 <= a AND a < 10 AND b = 1 OR +15 <= a AND a < 20 AND b = 3 +OR +1 <= a AND b = 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range a a 10 NULL 50 Using where; Using index +SELECT * FROM t2 WHERE +5 <= a AND a < 10 AND b = 2 OR +15 <= a AND a < 20 AND b = 3 +OR +1 <= a AND b = 1; +a b +1 1 +2 1 +3 1 +4 1 +5 1 +5 2 +6 1 +6 2 +7 1 +7 2 +8 1 +8 2 +9 1 +9 2 +10 1 +11 1 +12 1 +13 1 +14 1 +15 1 +15 3 +16 1 +16 3 +17 1 +17 3 +18 1 +18 3 +19 1 +19 3 +20 1 +EXPLAIN +SELECT * FROM t2 WHERE +5 <= a AND a < 10 AND b = 2 OR +15 <= a AND a < 20 AND b = 3 +OR +1 <= a AND b = 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range a a 10 NULL 50 Using where; Using index +SELECT * FROM t3 WHERE +5 <= a AND a < 10 AND b = 3 OR +a < 5 OR +a < 10; +a b +1 0 +2 0 +3 0 +4 0 +5 0 +6 0 +7 0 +8 0 +9 0 +EXPLAIN +SELECT * FROM t3 WHERE +5 <= a AND a < 10 AND b = 3 OR +a < 5 OR +a < 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t3 range a a 5 NULL 8 Using where; Using index +DROP TABLE t1, t2, t3; === modified file 'mysql-test/t/range.test' --- a/mysql-test/t/range.test 2008-03-27 02:18:46 +0000 +++ b/mysql-test/t/range.test 2009-10-09 09:30:40 +0000 @@ -1046,3 +1046,128 @@ explain select * from t2 where a=1000 an drop table t1, t2; +# +# Bug#42846: wrong result returned for range scan when using covering index +# +CREATE TABLE t1( a INT, b INT, KEY( a, b ) ); + +CREATE TABLE t2( a INT, b INT, KEY( a, b ) ); + +CREATE TABLE t3( a INT, b INT, KEY( a, b ) ); + +INSERT INTO t1( a, b ) +VALUES (0, 1), (1, 2), (1, 4), (2, 3), (5, 0), (9, 7); + +INSERT INTO t2( a, b ) +VALUES ( 1, 1), ( 2, 1), ( 3, 1), ( 4, 1), ( 5, 1), + ( 6, 1), ( 7, 1), ( 8, 1), ( 9, 1), (10, 1), + (11, 1), (12, 1), (13, 1), (14, 1), (15, 1), + (16, 1), (17, 1), (18, 1), (19, 1), (20, 1); + +INSERT INTO t2 SELECT a, 2 FROM t2 WHERE b = 1; +INSERT INTO t2 SELECT a, 3 FROM t2 WHERE b = 1; + +# To make range scan compelling to the optimizer +INSERT INTO t2 SELECT -1, -1 FROM t2; +INSERT INTO t2 SELECT -1, -1 FROM t2; +INSERT INTO t2 SELECT -1, -1 FROM t2; + +INSERT INTO t3 +VALUES (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), + (6, 0), (7, 0), (8, 0), (9, 0), (10, 0); + +# To make range scan compelling to the optimizer +INSERT INTO t3 SELECT * FROM t3 WHERE a = 10; +INSERT INTO t3 SELECT * FROM t3 WHERE a = 10; + + +# +# Problem#1 Test queries. Will give missing results unless Problem#1 is fixed. +# With one exception, they are independent of Problem#2. +# +SELECT * FROM t1 WHERE +3 <= a AND a < 5 OR +5 < a AND b = 3 OR +3 <= a; + +EXPLAIN +SELECT * FROM t1 WHERE +3 <= a AND a < 5 OR +5 < a AND b = 3 OR +3 <= a; + +# Query below: Tests both Problem#1 and Problem#2 (EXPLAIN differs as well) +SELECT * FROM t1 WHERE +3 <= a AND a < 5 OR +5 <= a AND b = 3 OR +3 <= a; + +EXPLAIN +SELECT * FROM t1 WHERE +3 <= a AND a < 5 OR +5 <= a AND b = 3 OR +3 <= a; + +SELECT * FROM t1 WHERE +3 <= a AND a <= 5 OR +5 <= a AND b = 3 OR +3 <= a; + +EXPLAIN +SELECT * FROM t1 WHERE +3 <= a AND a <= 5 OR +5 <= a AND b = 3 OR +3 <= a; + +SELECT * FROM t1 WHERE +3 <= a AND a <= 5 OR +3 <= a; + +EXPLAIN +SELECT * FROM t1 WHERE +3 <= a AND a <= 5 OR +3 <= a; + +# +# Problem#2 Test queries. +# These queries will give missing results if Problem#1 is fixed. +# But Problem#1 also hides this bug. +# +SELECT * FROM t2 WHERE +5 <= a AND a < 10 AND b = 1 OR +15 <= a AND a < 20 AND b = 3 +OR +1 <= a AND b = 1; + +EXPLAIN +SELECT * FROM t2 WHERE +5 <= a AND a < 10 AND b = 1 OR +15 <= a AND a < 20 AND b = 3 +OR +1 <= a AND b = 1; + +SELECT * FROM t2 WHERE +5 <= a AND a < 10 AND b = 2 OR +15 <= a AND a < 20 AND b = 3 +OR +1 <= a AND b = 1; + +EXPLAIN +SELECT * FROM t2 WHERE +5 <= a AND a < 10 AND b = 2 OR +15 <= a AND a < 20 AND b = 3 +OR +1 <= a AND b = 1; + +SELECT * FROM t3 WHERE +5 <= a AND a < 10 AND b = 3 OR +a < 5 OR +a < 10; + +EXPLAIN +SELECT * FROM t3 WHERE +5 <= a AND a < 10 AND b = 3 OR +a < 5 OR +a < 10; + +DROP TABLE t1, t2, t3; === modified file 'sql/opt_range.cc' --- a/sql/opt_range.cc 2009-10-08 15:36:36 +0000 +++ b/sql/opt_range.cc 2009-10-09 09:30:40 +0000 @@ -6512,6 +6512,63 @@ get_range(SEL_ARG **e1,SEL_ARG **e2,SEL_ } +/** + Combine two range expression under a common OR. On a logical level, the + transformation is key_or( expr1, expr2 ) => expr1 OR expr2. + + Both expressions are assumed to be in the SEL_ARG format. In a logic sense, + theformat is reminiscent of DNF, since an expression such as the following + + ( 1 < kp1 < 10 AND p1 ) OR ( 10 <= kp2 < 20 AND p2 ) + + where there is a key consisting of keyparts ( kp1, kp2, ..., kpn ) and p1 + and p2 are valid SEL_ARG expressions over keyparts kp2 ... kpn, is a valid + SEL_ARG condition. The disjuncts appear ordered by the minimum endpoint of + the first range and ranges must not overlap. It follows that they are also + ordered by maximum endpoints. Thus + + ( 1 < kp1 <= 2 AND ( kp2 = 2 OR kp2 = 3 ) ) OR kp1 = 3 + + Is a a valid SER_ARG expression for a key of at least 2 keyparts. + + For simplicity, we will assume that expr2 is a single range predicate, + i.e. on the form ( a < x < b AND ... ). It is easy to generalize to a + disjunction of several predicates by subsequently call key_or for each + disjunct. + + The algorithm iterates over each disjunct of expr1, and for each disjunct + where the first keypart's range overlaps with the first keypart's range in + expr2: + + If the predicates are equal for the rest of the keyparts, or if there are + no more, the range in expr2 has its endpoints copied in, and the SEL_ARG + node in expr2 is deallocated. If more ranges became connected in expr1, the + surplus is also dealocated. If they differ, two ranges are created. + + - The range leading up to the overlap. Empty if endpoints are equal. + + - The overlapping sub-range. May be the entire range if they are equal. + + Finally, there may be one more range if expr2's first keypart's range has a + greater maximum endpoint than the last range in expr1. + + For the overlapping sub-range, we recursively call key_or. Thus in order to + compute key_or of + + (1) ( 1 < kp1 < 10 AND 1 < kp2 < 10 ) + + (2) ( 2 < kp1 < 20 AND 4 < kp2 < 20 ) + + We create the ranges 1 < kp <= 2, 2 < kp1 < 10, 10 <= kp1 < 20. For the + first one, we simply hook on the condition for the second keypart from (1) + : 1 < kp2 < 10. For the second range 2 < kp1 < 10, key_or( 1 < kp2 < 10, 4 + < kp2 < 20 ) is called, yielding 1 < kp2 < 20. For the last range, we reuse + the range 4 < kp2 < 20 from (2) for the second keypart. The result is thus + + ( 1 < kp1 <= 2 AND 1 < kp2 < 10 ) OR + ( 2 < kp1 < 10 AND 1 < kp2 < 20 ) OR + ( 10 <= kp1 < 20 AND 4 < kp2 < 20 ) +*/ static SEL_ARG * key_or(RANGE_OPT_PARAM *param, SEL_ARG *key1,SEL_ARG *key2) { @@ -6663,7 +6720,21 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * key1=key1->tree_delete(save); } last->copy_min(tmp); - if (last->copy_min(key2) || last->copy_max(key2)) + bool full_range= last->copy_min(key2); + if (!full_range) + { + if (last->next && key2->cmp_max_to_min(last->next) >= 0) + { + last->max_value= last->next->min_value; + if (last->next->min_flag & NEAR_MIN) + last->max_flag&= ~NEAR_MAX; + else + last->max_flag|= NEAR_MAX; + } + else + full_range= last->copy_max(key2); + } + if (full_range) { // Full range key1->free_tree(); for (; key2 ; key2=key2->next) @@ -6673,8 +6744,6 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * return 0; } } - key2=key2->next; - continue; } if (cmp >= 0 && tmp->cmp_min_to_min(key2) < 0) --===============2058581674== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/martin.hansson@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: martin.hansson@stripped # target_branch: file:///data0/martin/bzr/bug42846/5.1bt/ # testament_sha1: 38472c288e11aebfac175874c6af75fa3bc588c8 # timestamp: 2009-10-09 11:30:47 +0200 # base_revision_id: mattias.jonsson@stripped\ # d06no60fal558l2p # # Begin bundle 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 --===============2058581674==--