From 5b360100d6dad4329b9fe59d15a2e0975ef4ff22 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Tue, 7 Jul 2026 11:46:14 +1000 Subject: [PATCH 1/3] MDEV-31182 Check for blob too in create_key_parts_for_pseudo_indexes BLOB and VARCHAR has keys prefixed with length of the payload. This length data itself has length HA_KEY_BLOB_LENGTH which is 2, as demonstrated for example in Field_blob::set_key_image and Field_varstring::set_key_image. In this patch, when preparing key info for a range analysis, we account for this extra HA_KEY_BLOB_LENGTH in the total key length for BLOB. Previously it was only done for VARCHAR. This way, when doing the actual range analysis, LIKE (Item_func_like::get_mm_leaf) can correctly identify that it is dealing with a BLOB, thereby allowing the space of 2 for the length data. This avoids later Field_blob::set_key_image reading the payload as length data and causing reading the wrong memory segment for the payload. --- mysql-test/main/type_blob.result | 16 ++++++++++++++++ mysql-test/main/type_blob.test | 17 +++++++++++++++++ sql/opt_range.cc | 5 +++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/type_blob.result b/mysql-test/main/type_blob.result index fb6132e74b76e..c866bebbee30b 100644 --- a/mysql-test/main/type_blob.result +++ b/mysql-test/main/type_blob.result @@ -1609,3 +1609,19 @@ Warning 1292 Truncated incorrect DECIMAL value: '#' Warning 1292 Truncated incorrect DECIMAL value: '#' Warning 1292 Truncated incorrect DECIMAL value: '#' DROP TABLE t1; +# +# MDEV-31182 ASAN errors in String_copier::well_formed_copy / calculate_cond_selectivity_for_table +# +SET histogram_type = DOUBLE_PREC_HB; +CREATE TABLE t (a INT); +INSERT INTO t VALUES (1),(2); +ANALYZE TABLE t PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t analyze status Engine-independent statistics collected +test.t analyze status OK +ALTER TABLE t MODIFY a BLOB, ALGORITHM=COPY; +SELECT a FROM t WHERE a LIKE '1%'; +a +1 +DROP TABLE t; +# end of 10.11 tests diff --git a/mysql-test/main/type_blob.test b/mysql-test/main/type_blob.test index bb90e7434d6ba..b5c3d4ca6ee6b 100644 --- a/mysql-test/main/type_blob.test +++ b/mysql-test/main/type_blob.test @@ -853,3 +853,20 @@ INSERT INTO t1 (c1) VALUES (-2),(-1),(1),(2),(3),(4),(5); SELECT c1 FROM t1 WHERE 'a' BETWEEN 0 AND (c1); SELECT c1 FROM t1 WHERE '#' BETWEEN c1 AND 0; DROP TABLE t1; + +--echo # +--echo # MDEV-31182 ASAN errors in String_copier::well_formed_copy / calculate_cond_selectivity_for_table +--echo # + +SET histogram_type = DOUBLE_PREC_HB; + +CREATE TABLE t (a INT); +INSERT INTO t VALUES (1),(2); +ANALYZE TABLE t PERSISTENT FOR ALL; +ALTER TABLE t MODIFY a BLOB, ALGORITHM=COPY; + +SELECT a FROM t WHERE a LIKE '1%'; + +DROP TABLE t; + +--echo # end of 10.11 tests diff --git a/sql/opt_range.cc b/sql/opt_range.cc index b042445a7aa1a..8707b490b6177 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -3241,7 +3241,8 @@ bool create_key_parts_for_pseudo_indexes(RANGE_OPT_PARAM *param, store_length= key_part->length; if (field->real_maybe_null()) store_length+= HA_KEY_NULL_LENGTH; - if (field->real_type() == MYSQL_TYPE_VARCHAR) + if (field->real_type() == MYSQL_TYPE_VARCHAR || + field->real_type() == MYSQL_TYPE_BLOB) store_length+= HA_KEY_BLOB_LENGTH; if (max_key_len < store_length) max_key_len= store_length; @@ -8930,7 +8931,7 @@ Item_func_like::get_mm_leaf(RANGE_OPT_PARAM *param, if (length != key_part->length + maybe_null) { - /* key packed with length prefix */ + /* BLOB or VARCHAR: key packed with length prefix */ offset+= HA_KEY_BLOB_LENGTH; field_length= length - HA_KEY_BLOB_LENGTH; } From 0bc1e0b041002e71b037cfab09ea4472b54c42ef Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Tue, 14 Jul 2026 16:38:22 +1000 Subject: [PATCH 2/3] MDEV-31182 Make Field_geom::set_key_image do nothing In fact, it should not be called at all, as GIS indexes do not store the whole data. Remove the only known callsite covered by a testcase (added in this commit), in print_key_part_value --- mysql-test/main/gis.result | 11 +++++++++++ mysql-test/main/gis.test | 13 +++++++++++++ sql/field.h | 2 +- sql/sql_type_geom.cc | 17 +++++++++++++++++ sql/sql_type_geom.h | 17 +++++++++++++---- 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/mysql-test/main/gis.result b/mysql-test/main/gis.result index 90c8f34e68d4f..9138141dae0bd 100644 --- a/mysql-test/main/gis.result +++ b/mysql-test/main/gis.result @@ -5602,3 +5602,14 @@ SELECT HEX(ST_GeometryN(0x000000000107000000010000000101000000, 1)); HEX(ST_GeometryN(0x000000000107000000010000000101000000, 1)) NULL # End of 10.6 tests +# +# MDEV-31182 ASAN errors in String_copier::well_formed_copy / calculate_cond_selectivity_for_table +# +CREATE TABLE t (a INT,b GEOMETRY NOT NULL,SPATIAL KEY idx (b)); +INSERT INTO t (a,b) VALUES (0,ST_GEOMFROMTEXT ('POINT(537323 35)')); +INSERT INTO t (a,b) VALUES (0,ST_GEOMFROMTEXT ('POINT(537323 35)')); +SET optimizer_trace='enabled=on'; +SELECT * FROM t WHERE MBRCONTAINS (ST_GEOMFROMTEXT ('LINESTRING (68466 35,139.68804 35.71411)'),b); +a b +DROP TABLE t; +# End of 10.11 tests diff --git a/mysql-test/main/gis.test b/mysql-test/main/gis.test index 24d5582ee3c8d..cd1b6257e9643 100644 --- a/mysql-test/main/gis.test +++ b/mysql-test/main/gis.test @@ -3574,3 +3574,16 @@ SELECT ST_GeomFromWKB(x'01070000000100000001070000000100000001070000000100000001 SELECT HEX(ST_GeometryN(0x000000000107000000010000000101000000, 1)); --echo # End of 10.6 tests + +--echo # +--echo # MDEV-31182 ASAN errors in String_copier::well_formed_copy / calculate_cond_selectivity_for_table +--echo # + +CREATE TABLE t (a INT,b GEOMETRY NOT NULL,SPATIAL KEY idx (b)); +INSERT INTO t (a,b) VALUES (0,ST_GEOMFROMTEXT ('POINT(537323 35)')); +INSERT INTO t (a,b) VALUES (0,ST_GEOMFROMTEXT ('POINT(537323 35)')); +SET optimizer_trace='enabled=on'; +SELECT * FROM t WHERE MBRCONTAINS (ST_GEOMFROMTEXT ('LINESTRING (68466 35,139.68804 35.71411)'),b); +DROP TABLE t; + +--echo # End of 10.11 tests diff --git a/sql/field.h b/sql/field.h index c31b38d78e623..9b117e5ff0b4a 100644 --- a/sql/field.h +++ b/sql/field.h @@ -1671,7 +1671,7 @@ class Field: public Value_source bool set_warning(Sql_condition::enum_warning_level, unsigned int code, int cuted_increment, ulong current_row=0) const; virtual void print_key_value(String *out, uint32 length); - void print_key_part_value(String *out, const uchar *key, uint32 length); + virtual void print_key_part_value(String *out, const uchar *key, uint32 length); void print_key_value_binary(String *out, const uchar* key, uint32 length); void raise_note_cannot_use_key_part(THD *thd, uint keynr, uint part, const LEX_CSTRING &op, diff --git a/sql/sql_type_geom.cc b/sql/sql_type_geom.cc index 6c46c80c05984..10ad5da527160 100644 --- a/sql/sql_type_geom.cc +++ b/sql/sql_type_geom.cc @@ -962,4 +962,21 @@ Binlog_type_info Field_geom::binlog_type_info() const field_charset(), type_handler_geom()->geometry_type()); } +void +Field_geom::print_key_part_value(String *out, const uchar* key, uint32) +{ + if (real_maybe_null() && *key) + { + /* + Byte 0 of key is the null-byte. If set, key is NULL. + Otherwise, print the key value starting immediately after the + null-byte + */ + out->append(NULL_clex_str); + return; + } + + out->append(STRING_WITH_LEN("unprintable_geometry_value")); +} + #endif // HAVE_SPATIAL diff --git a/sql/sql_type_geom.h b/sql/sql_type_geom.h index 14d3d062ef94a..7c7503a17bb38 100644 --- a/sql/sql_type_geom.h +++ b/sql/sql_type_geom.h @@ -419,6 +419,12 @@ class Field_geom :public Field_blob uint32 key_length() const override{ return packlength; } uint get_key_image(uchar *buff,uint length, const uchar *ptr_arg, imagetype type_arg) const override; + /* + GIS indexes do not store the whole data, so it makes no sense to + call Field_geom::set_key_image to restore the key to the column + */ + void set_key_image(const uchar *, uint) override + { DBUG_ASSERT(0); } /** Non-nullable GEOMETRY types cannot have defaults, @@ -429,11 +435,14 @@ class Field_geom :public Field_blob bool load_data_set_no_data(THD *thd, bool fixed_format) override; uint get_srid() const { return srid; } - void print_key_value(String *out, uint32 length) override - { - out->append(STRING_WITH_LEN("unprintable_geometry_value")); - } + /* + GIS indexes do not store the whole data, so it makes no sense to + call Field_geom::print_key_value to print the key value + */ + void print_key_value(String *, uint32) override + { DBUG_ASSERT(0); } Binlog_type_info binlog_type_info() const override; + void print_key_part_value(String *out, const uchar* key, uint32 length) override; }; #endif // HAVE_SPATIAL From 13c5c02c1b173e7581dd817a570889fbdb539a09 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Tue, 14 Jul 2026 16:38:38 +1000 Subject: [PATCH 3/3] MDEV-31182 Fix an MSAN issue introduced in the parent commit (this commit is split out for a targeted review that does not duplicate the review of its parent commit) The new testcase results in new coverage that exposes an MSAN uninitialised failure, where range->end_key.keypart_map is not assigned for GIS indexes in sel_arg_range_seq_next, so we accordingly do not use that keypart_map in the subsequent print_range call --- sql/opt_range.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 8707b490b6177..02a1ae3d13d33 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -16777,8 +16777,7 @@ void print_range(String *out, const KEY_PART_INFO *key_part, uint flag= range->range_flag; String key_name; key_name.set_charset(system_charset_info); - key_part_map keypart_map= range->start_key.keypart_map | - range->end_key.keypart_map; + key_part_map keypart_map= range->start_key.keypart_map; if (flag & GEOM_FLAG) { @@ -16794,6 +16793,7 @@ void print_range(String *out, const KEY_PART_INFO *key_part, return; } + keypart_map|= range->end_key.keypart_map; if (range->start_key.length) { print_key_value(out, key_part, range->start_key.key,