Skip to content

Commit 6fb907b

Browse files
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 is found because, in one case there is escaping, and in the other case escaping got removed. Solution ======== Since, there are 2 levels of unescaping being performed, 1. during sql parse of the context from information_schema, and 2. during read_string. So, we need to have 2 levels of escaping. First is done in the dump_mrr_info_calls() - here json_escape_to_string() is used. Second is done at the end of store_optimizer_context(), for the entire opt_context. Here a newly introduced function escape_json_for_sql_literal() is used which does escaping only for backslash, and single quote.
1 parent 4bad3f1 commit 6fb907b

4 files changed

Lines changed: 115 additions & 11 deletions

File tree

mysql-test/main/opt_context_replay_basic.result

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,4 +409,33 @@ id select_type table type possible_keys key key_len ref rows Extra
409409
1 SIMPLE t1 ALL btn NULL NULL NULL 4 Using where
410410
set optimizer_replay_context='';
411411
drop table t1;
412+
#
413+
# MDEV-39412: Failed to parse saved optimizer context: error reading ranges value
414+
#
415+
set optimizer_record_context=0;
416+
CREATE TABLE t1(
417+
a VARCHAR(8),
418+
b VARCHAR(8),
419+
KEY(A),
420+
KEY(B)
421+
);
422+
INSERT INTO t1 SELECT REPEAT('a',8), REPEAT('b',8) FROM seq_1_to_10;
423+
set optimizer_record_context=1;
424+
EXPLAIN
425+
SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
426+
ORDER BY a,b;
427+
id select_type table type possible_keys key key_len ref rows Extra
428+
1 SIMPLE t1 index_merge a,b a,b 35,35 NULL 10 Using sort_union(a,b); Using where; Using filesort
429+
select context into dumpfile "../../tmp/dump1.sql"
430+
from information_schema.optimizer_context;
431+
drop table t1;
432+
set optimizer_replay_context='opt_context';
433+
# Same query as above, must have same explain:
434+
EXPLAIN
435+
SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
436+
ORDER BY a,b;
437+
id select_type table type possible_keys key key_len ref rows Extra
438+
1 SIMPLE t1 index_merge a,b a,b 35,35 NULL 10 Using sort_union(a,b); Using where; Using filesort
439+
set optimizer_replay_context='';
440+
drop table t1;
412441
drop database db1;

mysql-test/main/opt_context_replay_basic.test

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,41 @@ set optimizer_replay_context='';
200200
--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql"
201201
drop table t1;
202202

203+
--echo #
204+
--echo # MDEV-39412: Failed to parse saved optimizer context: error reading ranges value
205+
--echo #
206+
set optimizer_record_context=0;
207+
208+
CREATE TABLE t1(
209+
a VARCHAR(8),
210+
b VARCHAR(8),
211+
KEY(A),
212+
KEY(B)
213+
);
214+
INSERT INTO t1 SELECT REPEAT('a',8), REPEAT('b',8) FROM seq_1_to_10;
215+
216+
set optimizer_record_context=1;
217+
EXPLAIN
218+
SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
219+
ORDER BY a,b;
220+
221+
select context into dumpfile "../../tmp/dump1.sql"
222+
from information_schema.optimizer_context;
223+
drop table t1;
224+
225+
--disable_query_log
226+
--disable_result_log
227+
--source "$MYSQLTEST_VARDIR/tmp/dump1.sql"
228+
--enable_query_log
229+
--enable_result_log
230+
set optimizer_replay_context='opt_context';
231+
--echo # Same query as above, must have same explain:
232+
EXPLAIN
233+
SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
234+
ORDER BY a,b;
235+
236+
set optimizer_replay_context='';
237+
--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql"
238+
drop table t1;
239+
203240
drop database db1;

sql/opt_context_store_replay.cc

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,18 @@ void dump_mrr_info_calls(List<Multi_range_read_const_call_record> *mrr_list,
233233
Json_writer_object irc_wrapper(ctx_writer);
234234
irc_wrapper.add("index_name", irc->idx_name);
235235

236+
List_iterator rc_li(irc->range_list);
237+
Json_writer_array ranges_wrapper(ctx_writer, "ranges");
238+
StringBuffer<128> escaped_range_info;
239+
while (const char *range_str= rc_li++)
236240
{
237-
Json_writer_array ranges_wrapper(ctx_writer, "ranges");
238-
List_iterator rc_li(irc->range_list);
239-
while (const char *range_str= rc_li++)
240-
ranges_wrapper.add(range_str, strlen(range_str));
241+
const String range_info(range_str, strlen(range_str),
242+
system_charset_info);
243+
json_escape_to_string(&range_info, &escaped_range_info);
244+
ranges_wrapper.add(escaped_range_info.c_ptr_safe(),
245+
escaped_range_info.length());
241246
}
247+
ranges_wrapper.end();
242248

243249
irc_wrapper.add("num_rows", irc->rows);
244250
{
@@ -518,6 +524,31 @@ static bool store_db_ddl(THD *thd, HASH *db_name_hash, String &script,
518524
return false;
519525
}
520526

527+
/*
528+
@brief
529+
Append the json "str" to sql_script, by escaping only backslash,
530+
and single quote
531+
*/
532+
static void escape_json_for_sql_literal(String &sql_script, const char *str,
533+
size_t len)
534+
{
535+
const char *end= str + len;
536+
for (; str < end; str++)
537+
{
538+
switch (*str)
539+
{
540+
case '\\':
541+
sql_script.append(STRING_WITH_LEN("\\\\"));
542+
break;
543+
case '\'':
544+
sql_script.append(STRING_WITH_LEN("\\'"));
545+
break;
546+
default:
547+
sql_script.append(*str);
548+
}
549+
}
550+
}
551+
521552
/*
522553
@brief
523554
Dump definitions, basic stats of all tables and views used by the
@@ -719,9 +750,12 @@ bool store_optimizer_context(THD *thd)
719750
const char *SET_OPT_CONTEXT_VAR= "set @opt_context=\'\n";
720751
const char *SET_REPLAY_CONTEXT_VAR=
721752
"set optimizer_replay_context=\'opt_context\'";
722-
String *s= const_cast<String *>(ctx_writer.output.get_string());
723753
sql_script.append(SET_OPT_CONTEXT_VAR, strlen(SET_OPT_CONTEXT_VAR));
724-
sql_script.append(*s);
754+
String *opt_context= const_cast<String *>(ctx_writer.output.get_string());
755+
// require extra escaping of the opt_ctx so as to counter the
756+
// unescaping done by sql parse
757+
escape_json_for_sql_literal(sql_script, opt_context->c_ptr_safe(),
758+
opt_context->length());
725759
sql_script.append(STRING_WITH_LEN("\n\';#opt_context_ends\n\n"));
726760
sql_script.append(SET_REPLAY_CONTEXT_VAR, strlen(SET_REPLAY_CONTEXT_VAR));
727761
sql_script.append(STRING_WITH_LEN(";\n\n"));

unittest/sql/json_reader-t.cc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,29 @@ int main(int args, char **argv)
3939
MEM_ROOT alloc;
4040
json_engine_t je;
4141
int rc;
42+
const char *esc_str_val= "a\bc";
4243
init_alloc_root(0, &alloc, 32768, 0, 0);
4344
mem_root_dynamic_array_init(&alloc, 0, &je.stack,
4445
sizeof(int), NULL, JSON_DEPTH_DEFAULT,
4546
JSON_DEPTH_INC, MYF(0));
4647
system_charset_info= &my_charset_utf8mb3_bin;
47-
const char *js_doc="{ \"str_val\": \"abc\", \"double_val\": 1234.5 }";
48+
const char *js_doc= "{ \"str_val\": \"abc\", \"double_val\": 1234.5, "
49+
"\"esc_str_val\": \"a\\bc\" }";
4850
json_scan_start(&je, &my_charset_utf8mb3_bin, (const uchar *) js_doc,
4951
(const uchar *) js_doc + strlen(js_doc));
5052

5153
char *parsed_name;
5254
double parsed_dbl;
55+
char *parsed_esc_str;
5356
Read_named_member array[]= {
54-
{"str_val", Read_string(&alloc, &parsed_name), false},
57+
{"str_val", Read_string(&alloc, &parsed_name), false},
5558
{"double_val", Read_double(&parsed_dbl), false},
56-
{NULL, Read_double(NULL), false }
57-
};
59+
{"esc_str_val", Read_string(&alloc, &parsed_esc_str), false},
60+
{NULL, Read_double(NULL), false}};
5861
String err_buf;
5962

60-
rc= json_read_object(&je, array, &err_buf);
63+
rc= json_read_object(&je, array, &err_buf) ||
64+
strcmp(parsed_esc_str, esc_str_val);
6165
ok(!rc, "Basic object read");
6266
free_root(&alloc, 0);
6367
#if 0

0 commit comments

Comments
 (0)