From 37d918967e7f417d744c886fd62a0b463991ce57 Mon Sep 17 00:00:00 2001 From: bsrikanth-mariadb Date: Thu, 7 May 2026 10:54:14 +0530 Subject: [PATCH] MDEV-39412: parse error reading tabs in ranges Note: while reading from information_schema.optimizer_context one level of unescaping is already done i.e. (\\t becomes \t or \\\\t becomes \\t) w.r.t the MDEV, there are 2 problems: - 1. When reading from the sql script file, json parser is not able to parse the range value in json_read_value() from json_lib.c "ranges": [ "(b\t\t\t\t\t\t) <= (b) <= (b???????)" ], mainly the \t\t stuff, and hence a warning. It also stops loading the context into memory. Since, a new table is created with empty data, and without context, we get Impossible WHERE noticed after reading const tables 2. There is unescaping call being made in read_string() from sql_json_lib.cc while parsing of the context. With this \\t was becoming \t. However, print_range() from opt_range.cc already does escaping of the values. The value "b\t\t\t" was in fact produced as "\b\\t\\t\\t". Later, we try to compare range values from the query and the context. Here a mismatch a found because, in one case there was escaping, and in the other case escaping got removed. Solutions ========= For Problem 1. have escaping for ranges. This should be done while dumping range values into the context. For Problem 2. Remove unscaping call in read_string(). --- .../main/opt_context_replay_basic.result | 30 ++++++++++++++ mysql-test/main/opt_context_replay_basic.test | 39 +++++++++++++++++++ sql/opt_context_store_replay.cc | 14 +++++-- sql/sql_json_lib.cc | 12 +----- unittest/sql/json_reader-t.cc | 14 ++++--- 5 files changed, 89 insertions(+), 20 deletions(-) diff --git a/mysql-test/main/opt_context_replay_basic.result b/mysql-test/main/opt_context_replay_basic.result index 34f9b4d6d2271..f344e36364330 100644 --- a/mysql-test/main/opt_context_replay_basic.result +++ b/mysql-test/main/opt_context_replay_basic.result @@ -272,5 +272,35 @@ set optimizer_replay_context='opt_context'; EXPLAIN SELECT MAX(a) FROM t1; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +set optimizer_replay_context=''; +drop table t1; +# +# MDEV-39412: Failed to parse saved optimizer context: error reading ranges value +# +set optimizer_record_context=0; +CREATE TABLE t1( +a VARCHAR(8), +b VARCHAR(8), +KEY(A), +KEY(B) +); +INSERT INTO t1 SELECT REPEAT('a',8), REPEAT('b',8) FROM seq_1_to_10; +set optimizer_record_context=1; +EXPLAIN +SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%' +ORDER BY a,b; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge a,b a,b 35,35 NULL 10 Using sort_union(a,b); Using where; Using filesort +select context into dumpfile "../../tmp/dump1.sql" +from information_schema.optimizer_context; +drop table t1; +set optimizer_replay_context='opt_context'; +# Same query as above, must have same explain: +EXPLAIN +SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%' +ORDER BY a,b; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge a,b a,b 35,35 NULL 10 Using sort_union(a,b); Using where; Using filesort +set optimizer_replay_context=''; drop table t1; drop database db1; diff --git a/mysql-test/main/opt_context_replay_basic.test b/mysql-test/main/opt_context_replay_basic.test index dcf85929c337b..5028738fcb097 100644 --- a/mysql-test/main/opt_context_replay_basic.test +++ b/mysql-test/main/opt_context_replay_basic.test @@ -116,6 +116,45 @@ set optimizer_replay_context='opt_context'; --echo # Same query as above, must have same explain: EXPLAIN SELECT MAX(a) FROM t1; +set optimizer_replay_context=''; +--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql" +drop table t1; + +--echo # +--echo # MDEV-39412: Failed to parse saved optimizer context: error reading ranges value +--echo # +set optimizer_record_context=0; + +CREATE TABLE t1( + a VARCHAR(8), + b VARCHAR(8), + KEY(A), + KEY(B) +); +INSERT INTO t1 SELECT REPEAT('a',8), REPEAT('b',8) FROM seq_1_to_10; + +set optimizer_record_context=1; +EXPLAIN +SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%' +ORDER BY a,b; + +select context into dumpfile "../../tmp/dump1.sql" +from information_schema.optimizer_context; +drop table t1; + +--disable_query_log +--disable_result_log +--source "$MYSQLTEST_VARDIR/tmp/dump1.sql" +--enable_query_log +--enable_result_log +set optimizer_replay_context='opt_context'; +--echo # Same query as above, must have same explain: +EXPLAIN +SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%' +ORDER BY a,b; + +set optimizer_replay_context=''; + --remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql" drop table t1; diff --git a/sql/opt_context_store_replay.cc b/sql/opt_context_store_replay.cc index e0f9594a0dbca..6402fdccec8d1 100644 --- a/sql/opt_context_store_replay.cc +++ b/sql/opt_context_store_replay.cc @@ -256,12 +256,18 @@ void dump_mrr_info_calls(List *mrr_list, Json_writer_object irc_wrapper(ctx_writer); irc_wrapper.add("index_name", irc->idx_name); + List_iterator rc_li(irc->range_list); + Json_writer_array ranges_wrapper(ctx_writer, "ranges"); + while (const char *range_str= rc_li++) { - Json_writer_array ranges_wrapper(ctx_writer, "ranges"); - List_iterator rc_li(irc->range_list); - while (const char *range_str= rc_li++) - ranges_wrapper.add(range_str, strlen(range_str)); + const String range_info(range_str, strlen(range_str), + system_charset_info); + StringBuffer<128> escaped_range_info; + json_escape_to_string(&range_info, &escaped_range_info); + ranges_wrapper.add(escaped_range_info.c_ptr_safe(), + escaped_range_info.length()); } + ranges_wrapper.end(); irc_wrapper.add("num_rows", irc->rows); { diff --git a/sql/sql_json_lib.cc b/sql/sql_json_lib.cc index 570db1f67bb72..8648a17ce6042 100644 --- a/sql/sql_json_lib.cc +++ b/sql/sql_json_lib.cc @@ -78,17 +78,7 @@ bool read_string(MEM_ROOT *mem_root, json_engine_t *je, const char *read_elem_ke if (check_reading_of_elem_key(je, read_elem_key, err_buf)) return true; - StringBuffer<128> val_buf; - if (json_unescape_to_string((const char *) je->value, je->value_len, - &val_buf)) - { - err_buf->append(STRING_WITH_LEN("un-escaping error of ")); - err_buf->append(read_elem_key, strlen(read_elem_key)); - err_buf->append(STRING_WITH_LEN(" element")); - return true; - } - - value= strdup_root(mem_root, val_buf.c_ptr_safe()); + value= strmake_root(mem_root, (const char *) je->value, je->value_len); return false; } diff --git a/unittest/sql/json_reader-t.cc b/unittest/sql/json_reader-t.cc index 72e6d1670957b..001ae35155856 100644 --- a/unittest/sql/json_reader-t.cc +++ b/unittest/sql/json_reader-t.cc @@ -39,25 +39,29 @@ int main(int args, char **argv) MEM_ROOT alloc; json_engine_t je; int rc; + const char *esc_str_val= "a\\bc"; init_alloc_root(0, &alloc, 32768, 0, 0); mem_root_dynamic_array_init(&alloc, 0, &je.stack, sizeof(int), NULL, JSON_DEPTH_DEFAULT, JSON_DEPTH_INC, MYF(0)); system_charset_info= &my_charset_utf8mb3_bin; - const char *js_doc="{ \"str_val\": \"abc\", \"double_val\": 1234.5 }"; + const char *js_doc= "{ \"str_val\": \"abc\", \"double_val\": 1234.5, " + "\"esc_str_val\": \"a\\bc\" }"; json_scan_start(&je, &my_charset_utf8mb3_bin, (const uchar *) js_doc, (const uchar *) js_doc + strlen(js_doc)); char *parsed_name; double parsed_dbl; + char *parsed_esc_str; Read_named_member array[]= { - {"str_val", Read_string(&alloc, &parsed_name), false}, + {"str_val", Read_string(&alloc, &parsed_name), false}, {"double_val", Read_double(&parsed_dbl), false}, - {NULL, Read_double(NULL), false } - }; + {"esc_str_val", Read_string(&alloc, &parsed_esc_str), false}, + {NULL, Read_double(NULL), false}}; String err_buf; - rc= json_read_object(&je, array, &err_buf); + rc= json_read_object(&je, array, &err_buf) || + strcmp(parsed_esc_str, esc_str_val); ok(!rc, "Basic object read"); free_root(&alloc, 0); #if 0