diff --git a/mysql-test/main/ctype_utf8.result b/mysql-test/main/ctype_utf8.result index 670cd9242b94b..dd1c12e4a383f 100644 --- a/mysql-test/main/ctype_utf8.result +++ b/mysql-test/main/ctype_utf8.result @@ -1521,9 +1521,9 @@ id a b 11 bbbbbb 60 SELECT id, a, b FROM t1 WHERE a BETWEEN 'aaaa' AND 'bbbbbb'; id a b +6 bbbbbb 40 8 aaaa 10 9 aaaa 50 -6 bbbbbb 40 11 bbbbbb 60 SELECT id, a FROM t1 WHERE a='bbbbbb'; id a diff --git a/mysql-test/main/ctype_utf8mb4.result b/mysql-test/main/ctype_utf8mb4.result index c97ba3b6d5369..26a273906d2ba 100644 --- a/mysql-test/main/ctype_utf8mb4.result +++ b/mysql-test/main/ctype_utf8mb4.result @@ -1546,9 +1546,9 @@ id a b 11 bbbbbb 60 SELECT id, a, b FROM t1 WHERE a BETWEEN 'aaaa' AND 'bbbbbb'; id a b +6 bbbbbb 40 8 aaaa 10 9 aaaa 50 -6 bbbbbb 40 11 bbbbbb 60 SELECT id, a FROM t1 WHERE a='bbbbbb'; id a diff --git a/mysql-test/main/range.result b/mysql-test/main/range.result index 188bae82f1a82..e304836dc4edf 100644 --- a/mysql-test/main/range.result +++ b/mysql-test/main/range.result @@ -3873,3 +3873,26 @@ DROP TABLE lineitem; # # End of 11.0 tests # +# +# MDEV-39843: Prefix index causes incorrect range query result +# +SET NAMES utf8mb4; +CREATE TABLE t0 (c0 LONGTEXT, KEY i0 (c0(1))); +INSERT INTO t0(c0) VALUES ('꙰dcF'), ('kER'); +SELECT c0 FROM t0 WHERE 'C0' <= t0.c0; +c0 +꙰dcF +kER +explain SELECT c0 FROM t0 WHERE 'C0' <= t0.c0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i0 NULL NULL NULL 2 Using where +DROP INDEX i0 ON t0; +SELECT c0 FROM t0 WHERE 'C0' <= t0.c0; +c0 +꙰dcF +kER +explain SELECT c0 FROM t0 WHERE 'C0' <= t0.c0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 2 Using where +DROP TABLE t0; +# End of 11.4 tests diff --git a/mysql-test/main/range.test b/mysql-test/main/range.test index dbd9d2b9cb5e2..0f9fa5549791e 100644 --- a/mysql-test/main/range.test +++ b/mysql-test/main/range.test @@ -2641,3 +2641,24 @@ DROP TABLE lineitem; --echo # --echo # End of 11.0 tests --echo # + +--echo # +--echo # MDEV-39843: Prefix index causes incorrect range query result +--echo # + +SET NAMES utf8mb4; + +CREATE TABLE t0 (c0 LONGTEXT, KEY i0 (c0(1))); +INSERT INTO t0(c0) VALUES ('꙰dcF'), ('kER'); + +SELECT c0 FROM t0 WHERE 'C0' <= t0.c0; +explain SELECT c0 FROM t0 WHERE 'C0' <= t0.c0; + +DROP INDEX i0 ON t0; + +SELECT c0 FROM t0 WHERE 'C0' <= t0.c0; +explain SELECT c0 FROM t0 WHERE 'C0' <= t0.c0; + +DROP TABLE t0; + +--echo # End of 11.4 tests diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 0d2949adcabdc..e92ffa7adfdcb 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -9701,6 +9701,25 @@ SEL_ARG *Field::stored_field_make_mm_leaf_bounded_int(RANGE_OPT_PARAM *param, } +/* + Check whether a partial (prefix) key segment cannot be used as a range + bound because of the collation. + + For a collation with a complex, non one-to-one character-to-weight mapping + (contractions, expansions or ignorable characters: MY_CS_NON1TO1) the sort + key of a character prefix does not preserve the ordering of the full value, + so neither a lower nor an upper prefix bound is sound. + Equality is unaffected and keeps using the prefix (the full condition is + always rechecked). +*/ +static bool prefix_key_part_unsafe_for_range(const KEY_PART *key_part, + const Field *field) +{ + return (key_part->flag & HA_PART_KEY_SEG) && + (field->charset()->state & MY_CS_NON1TO1); +} + + SEL_ARG *Field::stored_field_make_mm_leaf(RANGE_OPT_PARAM *param, KEY_PART *key_part, scalar_comparison_op op, @@ -9715,12 +9734,20 @@ SEL_ARG *Field::stored_field_make_mm_leaf(RANGE_OPT_PARAM *param, switch (op) { case SCALAR_CMP_LE: + if (prefix_key_part_unsafe_for_range(key_part, this)) + DBUG_RETURN(0); DBUG_RETURN(new (mem_root) SEL_ARG_LE(str, this)); case SCALAR_CMP_LT: + if (prefix_key_part_unsafe_for_range(key_part, this)) + DBUG_RETURN(0); DBUG_RETURN(new (mem_root) SEL_ARG_LT(thd, str, key_part, this, value)); case SCALAR_CMP_GT: + if (prefix_key_part_unsafe_for_range(key_part, this)) + DBUG_RETURN(0); DBUG_RETURN(new (mem_root) SEL_ARG_GT(thd, str, key_part, this, value)); case SCALAR_CMP_GE: + if (prefix_key_part_unsafe_for_range(key_part, this)) + DBUG_RETURN(0); DBUG_RETURN(new (mem_root) SEL_ARG_GE(thd, str, key_part, this, value)); case SCALAR_CMP_EQ: case SCALAR_CMP_EQUAL: