Skip to content

Commit 5f06c84

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 17dece4 commit 5f06c84

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
@@ -337,4 +337,33 @@ id select_type table type possible_keys key key_len ref rows Extra
337337
1 SIMPLE t1 ALL btn NULL NULL NULL 4 Using where
338338
set optimizer_replay_context='';
339339
drop table t1;
340+
#
341+
# MDEV-39412: Failed to parse saved optimizer context: error reading ranges value
342+
#
343+
set optimizer_record_context=0;
344+
CREATE TABLE t1(
345+
a VARCHAR(8),
346+
b VARCHAR(8),
347+
KEY(A),
348+
KEY(B)
349+
);
350+
INSERT INTO t1 SELECT REPEAT('a',8), REPEAT('b',8) FROM seq_1_to_10;
351+
set optimizer_record_context=1;
352+
EXPLAIN
353+
SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
354+
ORDER BY a,b;
355+
id select_type table type possible_keys key key_len ref rows Extra
356+
1 SIMPLE t1 index_merge a,b a,b 35,35 NULL 10 Using sort_union(a,b); Using where; Using filesort
357+
select context into dumpfile "../../tmp/dump1.sql"
358+
from information_schema.optimizer_context;
359+
drop table t1;
360+
set optimizer_replay_context='opt_context';
361+
# Same query as above, must have same explain:
362+
EXPLAIN
363+
SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
364+
ORDER BY a,b;
365+
id select_type table type possible_keys key key_len ref rows Extra
366+
1 SIMPLE t1 index_merge a,b a,b 35,35 NULL 10 Using sort_union(a,b); Using where; Using filesort
367+
set optimizer_replay_context='';
368+
drop table t1;
340369
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
@@ -256,12 +256,18 @@ void dump_mrr_info_calls(List<Multi_range_read_const_call_record> *mrr_list,
256256
Json_writer_object irc_wrapper(ctx_writer);
257257
irc_wrapper.add("index_name", irc->idx_name);
258258

259+
List_iterator rc_li(irc->range_list);
260+
Json_writer_array ranges_wrapper(ctx_writer, "ranges");
261+
StringBuffer<128> escaped_range_info;
262+
while (const char *range_str= rc_li++)
259263
{
260-
Json_writer_array ranges_wrapper(ctx_writer, "ranges");
261-
List_iterator rc_li(irc->range_list);
262-
while (const char *range_str= rc_li++)
263-
ranges_wrapper.add(range_str, strlen(range_str));
264+
const String range_info(range_str, strlen(range_str),
265+
system_charset_info);
266+
json_escape_to_string(&range_info, &escaped_range_info);
267+
ranges_wrapper.add(escaped_range_info.c_ptr_safe(),
268+
escaped_range_info.length());
264269
}
270+
ranges_wrapper.end();
265271

266272
irc_wrapper.add("num_rows", irc->rows);
267273
{
@@ -515,6 +521,31 @@ static bool store_db_ddl(THD *thd, HASH *db_name_hash, String &sql_script,
515521
return false;
516522
}
517523

524+
/*
525+
@brief
526+
Append the json "str" to sql_script, by escaping only backslash,
527+
and single quote
528+
*/
529+
static void escape_json_for_sql_literal(String &sql_script, const char *str,
530+
size_t len)
531+
{
532+
const char *end= str + len;
533+
for (; str < end; str++)
534+
{
535+
switch (*str)
536+
{
537+
case '\\':
538+
sql_script.append(STRING_WITH_LEN("\\\\"));
539+
break;
540+
case '\'':
541+
sql_script.append(STRING_WITH_LEN("\\'"));
542+
break;
543+
default:
544+
sql_script.append(*str);
545+
}
546+
}
547+
}
548+
518549
/*
519550
@brief
520551
Dump definitions, basic stats of all tables and views used by the
@@ -688,9 +719,12 @@ bool store_optimizer_context(THD *thd)
688719
const char *SET_OPT_CONTEXT_VAR= "set @opt_context=\'\n";
689720
const char *SET_REPLAY_CONTEXT_VAR=
690721
"set optimizer_replay_context=\'opt_context\'";
691-
String *s= const_cast<String *>(ctx_writer.output.get_string());
692722
sql_script.append(SET_OPT_CONTEXT_VAR, strlen(SET_OPT_CONTEXT_VAR));
693-
sql_script.append(*s);
723+
String *opt_context= const_cast<String *>(ctx_writer.output.get_string());
724+
// require extra escaping of the opt_ctx so as to counter the
725+
// unescaping done by sql parse
726+
escape_json_for_sql_literal(sql_script, opt_context->c_ptr_safe(),
727+
opt_context->length());
694728
sql_script.append(STRING_WITH_LEN("\n\';#opt_context_ends\n\n"));
695729
sql_script.append(SET_REPLAY_CONTEXT_VAR, strlen(SET_REPLAY_CONTEXT_VAR));
696730
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)