From 9320d8ae30c18420bef659618175836221d363ea Mon Sep 17 00:00:00 2001 From: Alexey Botchkov Date: Sun, 11 Dec 2016 01:12:33 +0400 Subject: MDEV-11453 JSON_CONTAINS returns incorrect values. The weird logic of json_contains was implemented. --- include/json_lib.h | 1 + mysql-test/r/func_json.result | 51 +++++++++ mysql-test/t/func_json.test | 17 +++ sql/item_jsonfunc.cc | 245 +++++++++++++++++++++++++++--------------- sql/item_jsonfunc.h | 8 +- strings/json_lib.c | 7 ++ 6 files changed, 237 insertions(+), 92 deletions(-) diff --git a/include/json_lib.h b/include/json_lib.h index b2ea39cd728..abef3e60db6 100644 --- a/include/json_lib.h +++ b/include/json_lib.h @@ -311,6 +311,7 @@ int json_skip_level(json_engine_t *j); */ #define json_value_scalar(je) ((je)->value_type > JSON_VALUE_ARRAY) + /* Look for the JSON PATH in the json string. Function can be called several times with same JSON/PATH to diff --git a/mysql-test/r/func_json.result b/mysql-test/r/func_json.result index bae7da495b0..c972057e37c 100644 --- a/mysql-test/r/func_json.result +++ b/mysql-test/r/func_json.result @@ -90,6 +90,57 @@ json_contains('"youth"', '"you"') 0 select json_contains('[1]', '[1]', '$', '$[0]'); ERROR 42000: Incorrect parameter count in the call to native function 'json_contains' +select json_contains('', '', '$'); +json_contains('', '', '$') +0 +select json_contains('null', 'null', '$'); +json_contains('null', 'null', '$') +1 +select json_contains('"10"', '"10"', '$'); +json_contains('"10"', '"10"', '$') +1 +select json_contains('"10"', '10', '$'); +json_contains('"10"', '10', '$') +0 +select json_contains('10.1', '10', '$'); +json_contains('10.1', '10', '$') +0 +select json_contains('10.0', '10', '$'); +json_contains('10.0', '10', '$') +1 +select json_contains('[1]', '1'); +json_contains('[1]', '1') +1 +select json_contains('[2, 1]', '1'); +json_contains('[2, 1]', '1') +1 +select json_contains('[2, [2, 3], 1]', '1'); +json_contains('[2, [2, 3], 1]', '1') +1 +select json_contains('[4, [2, 3], 1]', '2'); +json_contains('[4, [2, 3], 1]', '2') +1 +select json_contains('[2, 1]', '[1, 2]'); +json_contains('[2, 1]', '[1, 2]') +1 +select json_contains('[2, 1]', '[1, 0, 2]'); +json_contains('[2, 1]', '[1, 0, 2]') +0 +select json_contains('[2, 0, 3, 1]', '[1, 2]'); +json_contains('[2, 0, 3, 1]', '[1, 2]') +1 +select json_contains('{"b":[1,2], "a":1}', '{"a":1, "b":2}'); +json_contains('{"b":[1,2], "a":1}', '{"a":1, "b":2}') +1 +select json_contains('{"a":1}', '{}'); +json_contains('{"a":1}', '{}') +1 +select json_contains('[1, {"a":1}]', '{}'); +json_contains('[1, {"a":1}]', '{}') +1 +select json_contains('[1, {"a":1}]', '{"a":1}'); +json_contains('[1, {"a":1}]', '{"a":1}') +1 select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]"); json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]") 1 diff --git a/mysql-test/t/func_json.test b/mysql-test/t/func_json.test index 12e1c742a26..d3d75fa3913 100644 --- a/mysql-test/t/func_json.test +++ b/mysql-test/t/func_json.test @@ -36,6 +36,23 @@ select json_contains('"you"', '"you"'); select json_contains('"youth"', '"you"'); --error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT select json_contains('[1]', '[1]', '$', '$[0]'); +select json_contains('', '', '$'); +select json_contains('null', 'null', '$'); +select json_contains('"10"', '"10"', '$'); +select json_contains('"10"', '10', '$'); +select json_contains('10.1', '10', '$'); +select json_contains('10.0', '10', '$'); +select json_contains('[1]', '1'); +select json_contains('[2, 1]', '1'); +select json_contains('[2, [2, 3], 1]', '1'); +select json_contains('[4, [2, 3], 1]', '2'); +select json_contains('[2, 1]', '[1, 2]'); +select json_contains('[2, 1]', '[1, 0, 2]'); +select json_contains('[2, 0, 3, 1]', '[1, 2]'); +select json_contains('{"b":[1,2], "a":1}', '{"a":1, "b":2}'); +select json_contains('{"a":1}', '{}'); +select json_contains('[1, {"a":1}]', '{}'); +select json_contains('[1, {"a":1}]', '{"a":1}'); select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]"); select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[10]"); diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index adb0912f1fc..e3fa34cd72c 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -596,39 +596,162 @@ error: } -bool Item_func_json_contains::fix_fields(THD *thd, Item **ref) -{ - return alloc_tmp_paths(thd, arg_count-2, &paths, &tmp_paths) || - Item_int_func::fix_fields(thd, ref); -} - - void Item_func_json_contains::fix_length_and_dec() { a2_constant= args[1]->const_item(); a2_parsed= FALSE; - mark_constant_paths(paths, args+2, arg_count-2); + if (arg_count > 2) + path.set_constant_flag(args[2]->const_item()); Item_int_func::fix_length_and_dec(); } -void Item_func_json_contains::cleanup() +static int find_key_in_object(json_engine_t *j, json_string_t *key) { - if (tmp_paths) + const uchar *c_str= key->c_str; + + while (json_scan_next(j) == 0 && j->state != JST_OBJ_END) { - for (uint i= arg_count-2; i>0; i--) - tmp_paths[i-1].free(); - tmp_paths= 0; + DBUG_ASSERT(j->state == JST_KEY); + if (json_key_matches(j, key)) + return TRUE; + if (json_skip_key(j)) + return FALSE; + key->c_str= c_str; } - Item_int_func::cleanup(); + + return FALSE; +} + + +static int check_contains(json_engine_t *js, json_engine_t *value) +{ + json_engine_t loc_js; + bool set_js; + + switch (js->value_type) + { + case JSON_VALUE_OBJECT: + { + json_string_t key_name; + + if (value->value_type != JSON_VALUE_OBJECT) + return FALSE; + + loc_js= *js; + set_js= FALSE; + json_string_set_cs(&key_name, value->s.cs); + while (json_scan_next(value) == 0 && value->state != JST_OBJ_END) + { + const uchar *k_start, *k_end; + + DBUG_ASSERT(value->state == JST_KEY); + k_start= value->s.c_str; + while (json_read_keyname_chr(value) == 0) + k_end= value->s.c_str; + + if (value->s.error || json_read_value(value)) + return FALSE; + + if (set_js) + *js= loc_js; + else + set_js= TRUE; + + json_string_set_str(&key_name, k_start, k_end); + if (!find_key_in_object(js, &key_name) || + json_read_value(js) || + !check_contains(js, value)) + return FALSE; + } + + return value->state == JST_OBJ_END && !json_skip_level(js); + } + case JSON_VALUE_ARRAY: + if (value->value_type != JSON_VALUE_ARRAY) + { + while (json_scan_next(js) == 0 && js->state != JST_ARRAY_END) + { + DBUG_ASSERT(js->state == JST_VALUE); + if (json_read_value(js)) + return FALSE; + + if (check_contains(js, value)) + { + if (json_skip_level(js)) + return FALSE; + return TRUE; + } + if (value->s.error || js->s.error) + return FALSE; + } + return FALSE; + } + /* else */ + loc_js= *js; + set_js= FALSE; + while (json_scan_next(value) == 0 && value->state != JST_ARRAY_END) + { + DBUG_ASSERT(value->state == JST_VALUE); + if (json_read_value(value)) + return FALSE; + + if (set_js) + *js= loc_js; + else + set_js= TRUE; + if (!check_contains(js, value)) + return FALSE; + } + + return value->state == JST_ARRAY_END; + + case JSON_VALUE_STRING: + if (value->value_type != JSON_VALUE_STRING) + return FALSE; + /* + TODO: make proper json-json comparison here that takes excapint + into account. + */ + return value->value_len == js->value_len && + memcmp(value->value, js->value, value->value_len) == 0; + case JSON_VALUE_NUMBER: + if (value->value_type == JSON_VALUE_NUMBER) + { + double d_j, d_v; + char *end; + int err; + + d_j= my_strntod(js->s.cs, (char *) js->value, js->value_len, + &end, &err);; + d_v= my_strntod(value->s.cs, (char *) value->value, value->value_len, + &end, &err);; + + return (fabs(d_j - d_v) < 1e-12); + } + else + return FALSE; + + default: + break; + } + + /* + We have these not mentioned in the 'switch' above: + + case JSON_VALUE_TRUE: + case JSON_VALUE_FALSE: + case JSON_VALUE_NULL: + */ + return value->value_type == js->value_type; } longlong Item_func_json_contains::val_int() { String *js= args[0]->val_str(&tmp_js); - json_engine_t je; - uint n_arg; + json_engine_t je, ve; + int result; if ((null_value= args[0]->null_value)) return 0; @@ -648,54 +771,37 @@ longlong Item_func_json_contains::val_int() json_scan_start(&je, js->charset(),(const uchar *) js->ptr(), (const uchar *) js->ptr() + js->length()); - if (arg_count<3) /* No path specified. */ - { - if (json_read_value(&je)) - goto error; - String jv_str((const char *)je.value_begin, - je.value_end - je.value_begin, js->charset()); - return val->eq(&jv_str, js->charset()); - } - - for (n_arg=2; n_arg < arg_count; n_arg++) + if (arg_count>2) /* Path specified. */ { uint array_counters[JSON_DEPTH_LIMIT]; - json_path_with_flags *c_path= paths + n_arg - 2; - if (!c_path->parsed) + if (!path.parsed) { - String *s_p= args[n_arg]->val_str(tmp_paths+(n_arg-2)); + String *s_p= args[2]->val_str(&tmp_path); if (s_p && - json_path_setup(&c_path->p,s_p->charset(),(const uchar *) s_p->ptr(), - (const uchar *) s_p->ptr() + s_p->length())) + json_path_setup(&path.p,s_p->charset(),(const uchar *) s_p->ptr(), + (const uchar *) s_p->end())) goto error; - c_path->parsed= c_path->constant; + path.parsed= path.constant; } - - if (args[n_arg]->null_value) + if (args[2]->null_value) goto error; - json_scan_start(&je, js->charset(),(const uchar *) js->ptr(), - (const uchar *) js->ptr() + js->length()); - - c_path->cur_step= c_path->p.steps; - if (json_find_path(&je, &c_path->p, &c_path->cur_step, array_counters)) - { - /* Path wasn't found. */ - if (je.s.error) - goto error; - continue; - } - - if (json_read_value(&je)) + path.cur_step= path.p.steps; + if (json_find_path(&je, &path.p, &path.cur_step, array_counters)) goto error; - String jv_str((const char *)je.value_begin, - je.value_end - je.value_begin, js->charset()); - if (val->eq(&jv_str, js->charset())) - return 1; } + json_scan_start(&ve, val->charset(),(const uchar *) val->ptr(), + (const uchar *) val->end()); - return 0; + if (json_read_value(&je) || json_read_value(&ve)) + return FALSE; + + result= check_contains(&je, &ve); + if (je.s.error || ve.s.error) + goto error; + + return result; error: null_value= 1; @@ -2002,41 +2108,6 @@ static int append_json_path(String *str, const json_path_t *p) } -#ifdef DUMMY -static int json_path_compare(const json_path_t *a, const json_path_t *b) -{ - uint i, a_len= a->last_step - a->steps, b_len= b->last_step - b->steps; - - if (a_len > b_len) - return -2; - - for (i=0; i <= a_len; i++) - { - const json_path_step_t *sa= a->steps + i; - const json_path_step_t *sb= b->steps + i; - - if (!((sa->type & sb->type) & JSON_PATH_KEY_OR_ARRAY)) - return -1; - - if (sa->type & JSON_PATH_ARRAY) - { - if (!(sa->type & JSON_PATH_WILD) && sa->n_item != sb->n_item) - return -1; - } - else /* JSON_PATH_KEY */ - { - if (!(sa->type & JSON_PATH_WILD) && - (sa->key_end - sa->key != sb->key_end - sb->key || - memcmp(sa->key, sb->key, sa->key_end - sa->key) != 0)) - return -1; - } - } - - return b_len > a_len; -} -#endif /*DUMMY*/ - - static int json_path_compare(const json_path_t *a, const json_path_t *b) { const json_path_step_t *sa= a->steps + 1; diff --git a/sql/item_jsonfunc.h b/sql/item_jsonfunc.h index da9baf47648..e6f26d95b13 100644 --- a/sql/item_jsonfunc.h +++ b/sql/item_jsonfunc.h @@ -174,17 +174,15 @@ class Item_func_json_contains: public Item_int_func { protected: String tmp_js; - json_path_with_flags *paths; - String *tmp_paths; + json_path_with_flags path; + String tmp_path; bool a2_constant, a2_parsed; String tmp_val, *val; public: Item_func_json_contains(THD *thd, List &list): - Item_int_func(thd, list), tmp_paths(0) {} + Item_int_func(thd, list) {} const char *func_name() const { return "json_contains"; } - bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(); - void cleanup(); longlong val_int(); Item *get_copy(THD *thd, MEM_ROOT *mem_root) { return get_item_copy(thd, mem_root, this); } diff --git a/strings/json_lib.c b/strings/json_lib.c index a9498070195..015ce1f39f8 100644 --- a/strings/json_lib.c +++ b/strings/json_lib.c @@ -1182,6 +1182,13 @@ int json_skip_level(json_engine_t *j) int json_skip_key(json_engine_t *j) { + if (j->state == JST_KEY) + { + while (json_read_keyname_chr(j) == 0); + if (j->s.error) + return 1; + } + if (json_read_value(j)) return 1; -- cgit v1.2.1