diff --git a/mysql-test/main/range.result b/mysql-test/main/range.result index 6943a295acb46..a7796ea4b668a 100644 --- a/mysql-test/main/range.result +++ b/mysql-test/main/range.result @@ -3755,6 +3755,53 @@ id key1 3 2 drop table t1; # +# MDEV-39323: CROSS JOIN query returns wrong result with NOT BETWEEN +# +CREATE TABLE t1 ( +c0 float DEFAULT NULL, +c1 mediumint DEFAULT NULL, +c2 int DEFAULT NULL, +UNIQUE KEY c0 (c0), +UNIQUE KEY c2 (c2), +KEY i0 (c2, c1, c0), +KEY i1 (c2), +KEY i2 (c1) +); +INSERT INTO t1 VALUES +(567621000, -8388608, NULL), +(0.363988, 0, -1643936528), +(-422810000, 0, NULL), +(0, 0, 0), +(NULL, 0, NULL), +(NULL, 8388607, NULL), +(NULL, -8388608, NULL); +CREATE TABLE t2 (c0 decimal(10,0) unsigned DEFAULT NULL); +INSERT INTO t2 VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL); +SELECT t1.c0, t1.c1, t1.c2 FROM t2 CROSS JOIN t1 +WHERE NOT (1 BETWEEN t1.c0 AND t1.c2) AND t1.c1 = t1.c2; +c0 c1 c2 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +SELECT t1.c0, t1.c1, t1.c2 FROM t1 +WHERE NOT (1 BETWEEN t1.c0 AND t1.c2) AND t1.c1 = t1.c2; +c0 c1 c2 +0 0 0 +SELECT c0, c2 FROM t1 WHERE NOT (1 BETWEEN c0 AND c2); +c0 c2 +0 0 +0.363988 -1643936528 +567621000 NULL +SELECT c0, c2 FROM t1 WHERE 1 NOT BETWEEN c0 AND c2; +c0 c2 +0 0 +0.363988 -1643936528 +567621000 NULL +DROP TABLE t1, t2; +# # End of 10.11 tests # set global innodb_stats_persistent= @innodb_stats_persistent_save; diff --git a/mysql-test/main/range.test b/mysql-test/main/range.test index a77cef34e4c19..3822353ff119d 100644 --- a/mysql-test/main/range.test +++ b/mysql-test/main/range.test @@ -2556,6 +2556,41 @@ evalp $query; drop table t1; +--echo # +--echo # MDEV-39323: CROSS JOIN query returns wrong result with NOT BETWEEN +--echo # +CREATE TABLE t1 ( + c0 float DEFAULT NULL, + c1 mediumint DEFAULT NULL, + c2 int DEFAULT NULL, + UNIQUE KEY c0 (c0), + UNIQUE KEY c2 (c2), + KEY i0 (c2, c1, c0), + KEY i1 (c2), + KEY i2 (c1) +); +INSERT INTO t1 VALUES + (567621000, -8388608, NULL), + (0.363988, 0, -1643936528), + (-422810000, 0, NULL), + (0, 0, 0), + (NULL, 0, NULL), + (NULL, 8388607, NULL), + (NULL, -8388608, NULL); +CREATE TABLE t2 (c0 decimal(10,0) unsigned DEFAULT NULL); +INSERT INTO t2 VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL); + +SELECT t1.c0, t1.c1, t1.c2 FROM t2 CROSS JOIN t1 + WHERE NOT (1 BETWEEN t1.c0 AND t1.c2) AND t1.c1 = t1.c2; +SELECT t1.c0, t1.c1, t1.c2 FROM t1 + WHERE NOT (1 BETWEEN t1.c0 AND t1.c2) AND t1.c1 = t1.c2; +--sorted_result +SELECT c0, c2 FROM t1 WHERE NOT (1 BETWEEN c0 AND c2); +--sorted_result +SELECT c0, c2 FROM t1 WHERE 1 NOT BETWEEN c0 AND c2; + +DROP TABLE t1, t2; + --echo # --echo # End of 10.11 tests --echo # diff --git a/mysql-test/main/range_mrr_icp.result b/mysql-test/main/range_mrr_icp.result index 7a5a1405c7375..a6c171c291db9 100644 --- a/mysql-test/main/range_mrr_icp.result +++ b/mysql-test/main/range_mrr_icp.result @@ -3744,6 +3744,53 @@ id key1 3 2 drop table t1; # +# MDEV-39323: CROSS JOIN query returns wrong result with NOT BETWEEN +# +CREATE TABLE t1 ( +c0 float DEFAULT NULL, +c1 mediumint DEFAULT NULL, +c2 int DEFAULT NULL, +UNIQUE KEY c0 (c0), +UNIQUE KEY c2 (c2), +KEY i0 (c2, c1, c0), +KEY i1 (c2), +KEY i2 (c1) +); +INSERT INTO t1 VALUES +(567621000, -8388608, NULL), +(0.363988, 0, -1643936528), +(-422810000, 0, NULL), +(0, 0, 0), +(NULL, 0, NULL), +(NULL, 8388607, NULL), +(NULL, -8388608, NULL); +CREATE TABLE t2 (c0 decimal(10,0) unsigned DEFAULT NULL); +INSERT INTO t2 VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL); +SELECT t1.c0, t1.c1, t1.c2 FROM t2 CROSS JOIN t1 +WHERE NOT (1 BETWEEN t1.c0 AND t1.c2) AND t1.c1 = t1.c2; +c0 c1 c2 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +SELECT t1.c0, t1.c1, t1.c2 FROM t1 +WHERE NOT (1 BETWEEN t1.c0 AND t1.c2) AND t1.c1 = t1.c2; +c0 c1 c2 +0 0 0 +SELECT c0, c2 FROM t1 WHERE NOT (1 BETWEEN c0 AND c2); +c0 c2 +0 0 +0.363988 -1643936528 +567621000 NULL +SELECT c0, c2 FROM t1 WHERE 1 NOT BETWEEN c0 AND c2; +c0 c2 +0 0 +0.363988 -1643936528 +567621000 NULL +DROP TABLE t1, t2; +# # End of 10.11 tests # set global innodb_stats_persistent= @innodb_stats_persistent_save; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index b042445a7aa1a..8fb04d913e3cd 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -8621,6 +8621,9 @@ SEL_TREE *Item::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) } +/* + Disallow range creation when BETWEEN arguments' types don't match. +*/ bool Item_func_between::can_optimize_range_const(Item_field *field_item) const { @@ -8660,7 +8663,26 @@ Item_func_between::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) { Item_field *field_item= (Item_field*) (arguments()[i]->real_item()); if (!can_optimize_range_const(field_item)) + { + /* + For NOT BETWEEN the resulting tree is the OR of one tree per + NOT BETWEEN argument. Skipping a disjunct here is wrong + because the remaining tree wouldn't scan all rows that satisfy + the NOT BETWEEN. So bail here to avoid building a bad range. + */ + if (negated) + { + tree= nullptr; + break; + } + + /* + For BETWEEN, dropping a conjunct is fine because the + remaining tree is a superset of matching rows and the WHERE + clause will filter any extra rows out. + */ continue; + } SEL_TREE *tmp= get_full_func_mm_tree(param, field_item, (Item*)(intptr) i); if (negated)