1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-40165: JSON_EQUAL/JSON_NORMALIZE dont respect KILL QUERY

Or max_statement_time limit.

JSON_EQUAL/JSON_NORMALIZE also didn't report warnings
on invalid JSON inputs.

Change the json_normalize function to take a json_engine_t
as a argument and implement the sql/item_jsonfunc.cc calls
to have a json_engine_t.

To the JSON_EQUALS and JSON_NORMALIZE, add error handling
to produce an error when there was one. compare_nested_object,
part of JSON_OVERLAPS, uses a new json_engine and
copies the error/position to the returning function.

json_normalize expects the incoming je structure to have
a valid, or nullptr, killed_ptr, as json_start will reset this.
This commit is contained in:
Daniel Black
2026-06-27 15:02:07 +10:00
parent ca87d09917
commit 14c16e02b2
8 changed files with 94 additions and 46 deletions

View File

@@ -442,7 +442,7 @@ int json_locate_key(const char *js, const char *js_end,
const char **key_start, const char **key_end,
int *comma_pos);
int json_normalize(DYNAMIC_STRING *result,
int json_normalize(json_engine_t *je, DYNAMIC_STRING *result,
const char *s, size_t size, CHARSET_INFO *cs);
int json_skip_array_and_count(json_engine_t *j, int* n_item);

View File

@@ -26,6 +26,8 @@ select json_contains_path(@obj, 'one', '$.c');
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
select json_depth(@obj);
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
select json_equals(@obj, @arr);
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
select json_exists(@obj, '$.c');
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
select json_extract(@obj, '$.c');
@@ -44,6 +46,8 @@ select json_merge_patch(@obj, @obj);
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
select json_merge_preserve(@obj, @arr);
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
select json_normalize(@obj);
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
select json_overlaps(@obj, @arr);
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
select json_remove(@obj,'$.foo');

View File

@@ -26,6 +26,7 @@ select json_compact(@arr);
select json_contains(@obj, '"d"', '$.c');
select json_contains_path(@obj, 'one', '$.c');
select json_depth(@obj);
select json_equals(@obj, @arr);
select json_exists(@obj, '$.c');
select json_extract(@obj, '$.c');
select json_detailed(@arr);
@@ -35,6 +36,7 @@ select json_length(@arr);
select json_merge(@obj, @arr);
select json_merge_patch(@obj, @obj);
select json_merge_preserve(@obj, @arr);
select json_normalize(@obj);
select json_overlaps(@obj, @arr);
select json_remove(@obj,'$.foo');
select json_replace(@obj,'$.foo',1);

View File

@@ -10,12 +10,18 @@ NULL
select json_equals("", "");
json_equals("", "")
NULL
Warnings:
Warning 4037 Unexpected end of JSON text in argument 1 to function 'json_equals'
select json_equals("", 1);
json_equals("", 1)
NULL
Warnings:
Warning 4037 Unexpected end of JSON text in argument 1 to function 'json_equals'
select json_equals(now(), now());
json_equals(now(), now())
NULL
Warnings:
Warning 4038 Syntax error in JSON text in argument 1 to function 'json_equals' at position 5
select json_equals('{"a":[1, 2, 3]}', '{"a":[1, 2, 3, 4]}');
json_equals('{"a":[1, 2, 3]}', '{"a":[1, 2, 3, 4]}')
0
@@ -66,6 +72,8 @@ select json_equals('{"obj":{"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj
'{"obj":{"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"obj": {"key": "value"}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}') as 32_levels;
32_levels
NULL
Warnings:
Warning 4040 Limit of 32 on JSON nested structures depth is reached in argument 1 to function 'json_equals' at position 248
#
# test values from different charset
# (UTF-8 two-bytes vs. latin1 single high-byte)

View File

@@ -43,6 +43,8 @@ NULL
select json_normalize('{ "invalid": "no_close"');
json_normalize('{ "invalid": "no_close"')
NULL
Warnings:
Warning 4037 Unexpected end of JSON text in argument 1 to function 'json_normalize'
drop table t1;
drop view v1;
create table t1 (text varchar(200) character set 'latin1');

View File

@@ -610,8 +610,13 @@ bool Item_func_json_equals::fix_length_and_dec(THD *thd)
bool Item_func_json_equals::val_bool()
{
longlong result= 0;
int arg_num= 0;
String a_tmp, b_tmp;
THD *thd;
json_engine_t je;
if ((null_value= args[0]->null_value || args[1]->null_value))
return 1;
String *a= args[0]->val_json(&a_tmp);
String *b= args[1]->val_json(&b_tmp);
@@ -631,27 +636,31 @@ bool Item_func_json_equals::val_bool()
return 1;
}
if ((null_value= args[0]->null_value || args[1]->null_value))
{
null_value= 1;
goto end;
}
thd= current_thd;
JSON_DO_PAUSE_EXECUTION(thd, 0.0002);
je.killed_ptr= (uint32_t *) &thd->killed;
if (json_normalize(&a_res, a->ptr(), a->length(), a->charset()))
{
null_value= 1;
goto end;
}
if (json_normalize(&je, &a_res, a->ptr(), a->length(), a->charset()))
goto return_null;
if (json_normalize(&b_res, b->ptr(), b->length(), b->charset()))
{
null_value= 1;
goto end;
}
arg_num++;
if (json_normalize(&je, &b_res, b->ptr(), b->length(), b->charset()))
goto return_null;
result= strcmp(a_res.str, b_res.str) ? 0 : 1;
goto end;
return_null:
null_value= 1;
end:
if (je.s.error)
{
/* looks convoluted, but report_json_error is a macro */
if (arg_num != 0)
a= b;
report_json_error(a, &je, arg_num);
}
dynstr_free(&b_res);
dynstr_free(&a_res);
return result;
@@ -4582,6 +4591,8 @@ String* Item_func_json_objectagg::val_str(String* str)
String *Item_func_json_normalize::val_str(String *buf)
{
String tmp;
json_engine_t je;
THD *thd;
String *raw_json= args[0]->val_str(&tmp);
DYNAMIC_STRING normalized_json;
@@ -4595,22 +4606,27 @@ String *Item_func_json_normalize::val_str(String *buf)
if (null_value)
goto end;
if (json_normalize(&normalized_json,
thd= current_thd;
JSON_DO_PAUSE_EXECUTION(thd, 0.0002);
je.killed_ptr= (uint32_t *) &thd->killed;
if (json_normalize(&je, &normalized_json,
raw_json->ptr(), raw_json->length(),
raw_json->charset()))
{
null_value= 1;
goto end;
}
goto null_return;
buf->length(0);
buf->set_charset(collation.collation);
if (buf->append(normalized_json.str, normalized_json.length))
{
null_value= 1;
goto end;
}
goto null_return;
goto end;
null_return:
null_value= 1;
if (je.s.error)
report_json_error(raw_json, &je, 0);
end:
dynstr_free(&normalized_json);
return null_value ? NULL : buf;
@@ -4802,6 +4818,8 @@ int compare_nested_object(json_engine_t *js, json_engine_t *value)
json_skip_level(js);
const char *value_end= (const char*)value->s.c_str;
const char *js_end= (const char*)js->s.c_str;
json_engine_t je;
je.killed_ptr= js->killed_ptr;
String a(value_begin, value_end-value_begin,value->s.cs);
String b(js_begin, js_end-js_begin, js->s.cs);
@@ -4812,9 +4830,16 @@ int compare_nested_object(json_engine_t *js, json_engine_t *value)
{
goto error;
}
if (json_normalize(&a_res, a.ptr(), a.length(), value->s.cs) ||
json_normalize(&b_res, b.ptr(), b.length(), value->s.cs))
if (json_normalize(&je, &a_res, a.ptr(), a.length(), value->s.cs))
{
value->s.error= je.s.error;
value->s.c_str= je.s.c_str;
goto error;
}
if (json_normalize(&je, &b_res, b.ptr(), b.length(), value->s.cs))
{
js->s.error= je.s.error;
js->s.c_str= je.s.c_str;
goto error;
}

View File

@@ -753,28 +753,29 @@ json_norm_parse_end:
static int
json_norm_build(struct json_norm_value *root,
json_norm_build(json_engine_t *je, struct json_norm_value *root,
const char *s, size_t size, CHARSET_INFO *cs)
{
int err= 0;
json_engine_t je;
volatile const uint32_t *killed_ptr= je->killed_ptr;
DBUG_ASSERT(s);
memset(&je, 0x00, sizeof(je));
memset(root, 0x00, sizeof(struct json_norm_value));
root->type= JSON_VALUE_UNINITIALIZED;
err= json_scan_start(&je, cs, (const uchar *)s, (const uchar *)(s + size));
if (json_read_value(&je))
/* json_scan_start clears the killed_ptr */
err= json_scan_start(je, cs, (const uchar *)s, (const uchar *)(s + size));
je->killed_ptr= killed_ptr;
if (json_read_value(je))
return err;
err= json_norm_value_init(root, &je);
err= json_norm_value_init(root, je);
if (root->type == JSON_VALUE_OBJECT ||
root->type == JSON_VALUE_ARRAY)
{
err= json_norm_parse(root, &je);
err= json_norm_parse(root, je);
if (err)
return err;
}
@@ -783,7 +784,7 @@ json_norm_build(struct json_norm_value *root,
int
json_normalize(DYNAMIC_STRING *result,
json_normalize(json_engine_t *je, DYNAMIC_STRING *result,
const char *s, size_t size, CHARSET_INFO *cs)
{
int err= 0;
@@ -833,7 +834,7 @@ json_normalize(DYNAMIC_STRING *result,
goto json_normalize_end;
}
err= json_norm_build(&root, in, in_size, &my_charset_utf8mb4_bin);
err= json_norm_build(je, &root, in, in_size, &my_charset_utf8mb4_bin);
if (err)
goto json_normalize_end;

View File

@@ -25,12 +25,13 @@ check_json_normalize(const char *in, const char *expected)
{
int err;
DYNAMIC_STRING result;
json_engine_t je= {0};
CHARSET_INFO *cs= &my_charset_utf8mb4_general_ci;
je.killed_ptr= NULL;
init_dynamic_string(&result, NULL, 0, 0);
err= json_normalize(&result, in, strlen(in), cs);
err= json_normalize(&je, &result, in, strlen(in), cs);
ok(err == 0, "normalize err?");
@@ -46,26 +47,29 @@ static void
test_json_normalize_invalid(void)
{
DYNAMIC_STRING result;
json_engine_t je= {0};
CHARSET_INFO *cs= &my_charset_utf8mb4_general_ci;
init_dynamic_string(&result, NULL, 0, 0);
ok(json_normalize(&result, STRING_WITH_LEN(""), cs) != 0,
ok(json_normalize(&je, &result, STRING_WITH_LEN(""), cs) != 0,
"expected normalized error");
dynstr_free(&result);
memset(&je, 0, sizeof(je));
init_dynamic_string(&result, NULL, 0, 0);
ok(json_normalize(&result, STRING_WITH_LEN("["), cs) != 0,
ok(json_normalize(&je, &result, STRING_WITH_LEN("["), cs) != 0,
"expected normalized error");
dynstr_free(&result);
memset(&je, 0, sizeof(je));
init_dynamic_string(&result, NULL, 0, 0);
ok(json_normalize(&result, STRING_WITH_LEN("}"), cs) != 0,
ok(json_normalize(&je, &result, STRING_WITH_LEN("}"), cs) != 0,
"expected normalized error");
dynstr_free(&result);
memset(&je, 0, sizeof(je));
init_dynamic_string(&result, NULL, 0, 0);
ok(json_normalize(&result, NULL, 0, cs) != 0,
ok(json_normalize(&je, &result, NULL, 0, cs) != 0,
"expected normalized error");
dynstr_free(&result);
}
@@ -200,17 +204,19 @@ test_json_normalize_non_utf8(void)
const char utf8[]= { 0x22, 0xC3, 0x8A, 0x22, 0x00 };
const char latin[] = { 0x22, 0xCA, 0x22, 0x00 };
DYNAMIC_STRING result;
json_engine_t je= {0};
CHARSET_INFO *cs_utf8= &my_charset_utf8mb4_bin;
CHARSET_INFO *cs_latin= &my_charset_latin1;
init_dynamic_string(&result, NULL, 0, 0);
err= json_normalize(&result, utf8, strlen(utf8), cs_utf8);
err= json_normalize(&je, &result, utf8, strlen(utf8), cs_utf8);
ok(err == 0, "normalize err?");
ok((strcmp(utf8, result.str) == 0), "utf8 round trip");
dynstr_free(&result);
memset(&je, 0, sizeof(je));
init_dynamic_string(&result, NULL, 0, 0);
err= json_normalize(&result, latin, strlen(latin), cs_latin);
err= json_normalize(&je, &result, latin, strlen(latin), cs_latin);
ok(err == 0, "normalize err?");
ok((strcmp(utf8, result.str) == 0), "latin to utf8 round trip");
dynstr_free(&result);