diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index e17c140e5ca..f5a1cf44b81 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -2295,5 +2295,16 @@ SELECT * FROM JSON_TABLE('{"foo":["bar","qux"]}','$**.*[0]' COLUMNS(col1 CHAR(8) col1 bar # +# MDEV-29212: json_overlaps() does not check nested key-value pair correctly +# +SET @json1 = '{"kk":{"k1":"v1","k2":"v2"}}'; +SET @json2 = '{"kk":{"k1":"v1","k2":"v2","k3":"v3"}}'; +SELECT JSON_OVERLAPS(@json2, @json1); +JSON_OVERLAPS(@json2, @json1) +0 +SELECT JSON_OVERLAPS(@json1, @json2); +JSON_OVERLAPS(@json1, @json2) +0 +# # End of 10.9 Test # diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index f8ef72ffca7..9459f586bce 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -1547,6 +1547,15 @@ SELECT JSON_EXISTS(@json, '$[2][2][1 to 4]'); SELECT * FROM JSON_TABLE('{"foo":["bar","qux"]}','$**.*[0]' COLUMNS(col1 CHAR(8) PATH '$[0]')) AS jt; +--echo # +--echo # MDEV-29212: json_overlaps() does not check nested key-value pair correctly +--echo # + +SET @json1 = '{"kk":{"k1":"v1","k2":"v2"}}'; +SET @json2 = '{"kk":{"k1":"v1","k2":"v2","k3":"v3"}}'; +SELECT JSON_OVERLAPS(@json2, @json1); +SELECT JSON_OVERLAPS(@json1, @json2); + --echo # --echo # End of 10.9 Test --echo # diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 8f751717570..97cae6cc0a9 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -4360,7 +4360,7 @@ bool json_compare_arr_and_obj(json_engine_t *js, json_engine_t *value) return TRUE; *value= loc_val; } - if (!json_value_scalar(js)) + if (js->value_type == JSON_VALUE_ARRAY) json_skip_level(js); } return FALSE; @@ -4446,76 +4446,131 @@ int json_find_overlap_with_array(json_engine_t *js, json_engine_t *value, } +int compare_nested_object(json_engine_t *js, json_engine_t *value) +{ + int result= 0; + const char *value_begin= (const char*)value->s.c_str-1; + const char *js_begin= (const char*)js->s.c_str-1; + json_skip_level(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; + + String a(value_begin, value_end-value_begin,value->s.cs); + String b(js_begin, js_end-js_begin, js->s.cs); + + DYNAMIC_STRING a_res, b_res; + if (init_dynamic_string(&a_res, NULL, 4096, 1024) || + init_dynamic_string(&b_res, NULL, 4096, 1024)) + { + 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)) + { + goto error; + } + + result= strcmp(a_res.str, b_res.str) ? 0 : 1; + + error: + dynstr_free(&a_res); + dynstr_free(&b_res); + + return MY_TEST(result); +} int json_find_overlap_with_object(json_engine_t *js, json_engine_t *value, bool compare_whole) { if (value->value_type == JSON_VALUE_OBJECT) { - /* Find at least one common key-value pair */ - json_string_t key_name; - bool found_key= false, found_value= false; - json_engine_t loc_js= *js; - const uchar *k_start, *k_end; - - json_string_set_cs(&key_name, value->s.cs); - - while (json_scan_next(value) == 0 && value->state == JST_KEY) + if (compare_whole) { - k_start= value->s.c_str; - do + return compare_nested_object(js, value); + } + else + { + /* Find at least one common key-value pair */ + json_string_t key_name; + bool found_key= false, found_value= false; + json_engine_t loc_js= *js; + const uchar *k_start, *k_end; + + json_string_set_cs(&key_name, value->s.cs); + + while (json_scan_next(value) == 0 && value->state == JST_KEY) { - k_end= value->s.c_str; - } while (json_read_keyname_chr(value) == 0); + k_start= value->s.c_str; + do + { + k_end= value->s.c_str; + } while (json_read_keyname_chr(value) == 0); - if (unlikely(value->s.error)) - return FALSE; - - json_string_set_str(&key_name, k_start, k_end); - found_key= find_key_in_object(js, &key_name); - found_value= 0; - - if (found_key) - { - if (json_read_value(js) || json_read_value(value)) + if (unlikely(value->s.error)) return FALSE; - /* - The value of key-value pair can be an be anything. If it is an object - then we need to compare the whole value and if it is an array then - we need to compare the elements in that order. So set compare_whole - to true. - */ - if (js->value_type == value->value_type) - found_value= check_overlaps(js, value, true); - if (found_value) + json_string_set_str(&key_name, k_start, k_end); + found_key= find_key_in_object(js, &key_name); + found_value= 0; + + if (found_key) { - if (!compare_whole) + if (json_read_value(js) || json_read_value(value)) + return FALSE; + + /* + The value of key-value pair can be an be anything. If it is an object + then we need to compare the whole value and if it is an array then + we need to compare the elements in that order. So set compare_whole + to true. + */ + if (js->value_type == value->value_type) + found_value= check_overlaps(js, value, true); + if (found_value) + { + /* + We have found at least one common key-value pair now. + No need to check for more key-value pairs. So skip remaining + jsons and return TRUE. + */ + json_skip_current_level(js, value); return TRUE; - *js= loc_js; + } + else + { + /* + Key is found but value is not found. We have already + exhausted both values for current key. Hence "reset" + only js (first argument i.e json document) and + continue. + */ + *js= loc_js; + continue; + } } else { - if (compare_whole) - { - json_skip_current_level(js, value); + /* + key is not found. So no need to check for value for that key. + Read the value anyway so we get the "type" of json value. + If is is non-scalar then skip the entire value + (scalar values get exhausted while reading so no need to skip them). + Then reset the json doc again. + */ + if (json_read_value(value)) return FALSE; - } + if (!json_value_scalar(value)) + json_skip_level(value); *js= loc_js; } } - else - { - if (compare_whole) - { - json_skip_current_level(js, value); - return FALSE; - } - json_skip_key(value); - *js= loc_js; - } + /* + At this point we have already returned true if any intersection exists. + So skip jsons if not exhausted and return false. + */ + json_skip_current_level(js, value); + return FALSE; } - json_skip_current_level(js, value); - return compare_whole ? TRUE : FALSE; } else if (value->value_type == JSON_VALUE_ARRAY) {