diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index d001bb326f8..11872c920ed 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -4947,3 +4947,180 @@ JSON_KEY_VALUE('{"key1":{"a":1, "b": [1,2,3, {"some_key":"some_val", "c":3}]}, " COLUMNS (k VARCHAR(20) PATH '$.key', v VARCHAR(20) PATH '$.value', id FOR ORDINALITY)) AS jt; k v id # End of 11.2 test +# +# Beginning of 11.2 tests +# +# MDEV-26182: Implement json_array_intersect() +# +# JSON_ARRAY_INTERSECT() +# Scalar as elements +SET @json1= '[1,2,3]'; +SET @json2= '[1,2,3]'; +SELECT json_array_intersect(@json1, @json2); +json_array_intersect(@json1, @json2) +[1, 2, 3] +SET @json1= '[1,2,3]'; +SET @json2= '[1,2,4]'; +SELECT json_array_intersect(@json1, @json2); +json_array_intersect(@json1, @json2) +[1, 2] +SET @json1= '["abc","def","ghi"]'; +SET @json2= '["xyz", "abc", "tuv"]'; +SELECT json_array_intersect(@json1, @json2); +json_array_intersect(@json1, @json2) +["abc"] +SET @obj1= '[true]'; +SET @obj2= '[false, "true"]'; +select json_array_intersect(@obj1, @obj2); +json_array_intersect(@obj1, @obj2) +NULL +SET @obj1= '[true]'; +SET @obj2= '[false, true]'; +select json_array_intersect(@obj1, @obj2); +json_array_intersect(@obj1, @obj2) +[true] +SET @obj1= '[null, true]'; +SET @obj2= '[false, null]'; +select json_array_intersect(@obj1, @obj2); +json_array_intersect(@obj1, @obj2) +[null] +# array as elements +SET @json1= '[6,6,6]'; +SET @json2= '[[1,2,3],[4,5,6],[1,3,2]]'; +SELECT json_array_intersect(@json1, @json2); +json_array_intersect(@json1, @json2) +NULL +SET @json1= '[[1,2,3],[4,5,6],[1,3,2]]'; +SET @json2= '[[1,2,3],[4,5,6],[1,3,2]]'; +SELECT json_array_intersect(@json1, @json2); +json_array_intersect(@json1, @json2) +[[1, 2, 3], [4, 5, 6], [1, 3, 2]] +SET @json1= '[[1,2,3],[4,5,6],[1,3,2]]'; +SET @json2= '[[1,2,3],[4,5,6],[1,4,2]]'; +SELECT json_array_intersect(@json1, @json2); +json_array_intersect(@json1, @json2) +[[1, 2, 3], [4, 5, 6]] +# object as elements +SET @json1 = '[{"k1":"v1","k2":"v2"},{"k2":"v2"}]'; +SET @json2 = '[{"kkey1":"vvalue1"},{"k2":"v2","k1":"v1"}]'; +SELECT json_array_intersect(@json1, @json2); +json_array_intersect(@json1, @json2) +[{"k2": "v2", "k1": "v1"}] +SET @json1 = '[{"k1":"v1","k2":"v2"},{"k2":"v2","k1":"v1"}]'; +SET @json2 = '[{"k1":"v1","k2":"v2"},{"k1":"v1","k2":"v2"}]'; +SELECT json_array_intersect(@json1, @json2); +json_array_intersect(@json1, @json2) +[{"k1": "v1", "k2": "v2"}, {"k1": "v1", "k2": "v2"}] +# multi type elements +SET @obj1= '[1,2,3, "abc", "xyz", {"key1":"val1"}, {"key2":"val2"}, [1,2,3]]'; +SET @obj2= '[3.0, 4, 5, "abc", {"key1":"val1"}, [3,2,1]]'; +select json_array_intersect(@obj1, @obj2); +json_array_intersect(@obj1, @obj2) +[3.0, "abc", {"key1": "val1"}] +SET @obj1= '[1, 2, 3, "abc", "xyz", {"key1": {"key2" : [1,2,3] } }, [4,5,6] ]'; +SET @obj2= '[3.0, 4, 5, "abc", {"key1": {"key2" : [3,2,1]} }, {"key1": {"key2" : [1,2,3] } }, [4,5,6], [6,5,4] ]'; +select json_array_intersect(@obj1, @obj2); +json_array_intersect(@obj1, @obj2) +[3.0, "abc", {"key1": {"key2": [1, 2, 3]}}, [4, 5, 6]] +# Checking duplicates +SET @obj1= '[1, 2, 3, 3, 3.0, "abc", true, true, {"key1":"val1"}]'; +SET @obj2= '[3.0, 3, 5, "abc", "abc", true, {"key2":"val2"}, {"key1":"val1"}, {"key1":"val2"}]'; +select json_array_intersect(@obj1, @obj2); +json_array_intersect(@obj1, @obj2) +[3.0, 3, "abc", true, {"key1": "val1"}] +# Checking Syntax error for JSON_ARRAY_INTERSECT() +SET @obj1= '[1, 2, 3, 3, 3.0, "abc", true, true, {"key1":"val1" ]'; +SET @obj2= '[3.0, 3, 5, "abc", "abc", true, {"key2":"val2"}, {"key1":"val1"}, {"key1":"val2"}]'; +select json_array_intersect(@obj1, @obj2); +json_array_intersect(@obj1, @obj2) +NULL +Warnings: +Warning 4038 Syntax error in JSON text in argument 1 to function 'json_array_intersect' at position 53 +# Checking incorrect type for input +SET @obj1= '{"key1": "val1"}'; +SET @arr1= '[ 1, 2, 3 ]'; +SET @num1= '2'; +SET @str1= '"abc"'; +SET @bool1= 'true'; +select json_array_intersect(@obj1, @arr1); +json_array_intersect(@obj1, @arr1) +NULL +select json_array_intersect(@arr1, @obj1); +json_array_intersect(@arr1, @obj1) +NULL +select json_array_intersect(@arr1, @num1); +json_array_intersect(@arr1, @num1) +NULL +select json_array_intersect(@num1, @bool1); +json_array_intersect(@num1, @bool1) +NULL +# JSON_OBJECT_FILTER_KEYS() +SET @obj1= '{ "a": 1, "b": 2, "c": 3}'; +SET @obj2= '{"b" : 10, "c": 20, "d": 30}'; +SELECT JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))); +JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))) +{"b": 2, "c": 3} +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @obj2= '{"b" : 10, "c": 20, "d": 30}'; +SELECT JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))); +JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))) +{"b": {"key1": {"key2": "val2"}}, "c": [1, 2, 3]} +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @arr2='["x", "y", "z"]'; +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @arr2); +JSON_OBJECT_FILTER_KEYS(@obj1, @arr2) +NULL +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @arr2='["key2", "key1", "b"]'; +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @arr1); +JSON_OBJECT_FILTER_KEYS(@obj1, @arr1) +NULL +# Incorrect type in input returns NULL +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @obj1); +JSON_OBJECT_FILTER_KEYS(@obj1, @obj1) +NULL +SELECT JSON_OBJECT_FILTER_KEYS(@arr1, @arr1); +JSON_OBJECT_FILTER_KEYS(@arr1, @arr1) +NULL +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @scalar1='2'; +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @scalar1); +JSON_OBJECT_FILTER_KEYS(@obj1, @scalar1) +NULL +# Checking syntax error +SET @obj1= '{ "a": 1, "b": 2, "c": 3}'; +SET @obj2= '{"b" : 10, "c": 20, "d" 30}'; +SELECT JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))); +JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))) +NULL +Warnings: +Warning 4038 Syntax error in JSON text in argument 1 to function 'json_keys' at position 25 +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @arr2= '[ "key2", "key1", "b" '; +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @arr1); +JSON_OBJECT_FILTER_KEYS(@obj1, @arr1) +NULL +# JSON_OBJECT_TO_ARRAY() +SET @obj1= '{ "a": [1, 2, 3], "b": { "key1":"val1", "key2": {"key3":"val3"} }, "c": 3, "d" : 1, "e": "xyz", "f": true, "g" : null}'; +SELECT JSON_OBJECT_TO_ARRAY(@obj1); +JSON_OBJECT_TO_ARRAY(@obj1) +[["a", [1, 2, 3]], ["b", {"key1": "val1", "key2": {"key3": "val3"}}], ["c", 3], ["d", 1], ["e", "xyz"], ["f", true], ["g", null]] +SET @obj1= '{ "a": [1, 2, 3], "b": { "key1":"val1", "key2": [1, 2, 3] }, "c": 3, "d" : 1, "e": "xyz", "f": true, "g" : null}'; +SELECT JSON_OBJECT_TO_ARRAY(@obj1); +JSON_OBJECT_TO_ARRAY(@obj1) +[["a", [1, 2, 3]], ["b", {"key1": "val1", "key2": [1, 2, 3]}], ["c", 3], ["d", 1], ["e", "xyz"], ["f", true], ["g", null]] +# Checking syntax error +SET @obj1= '{ "a": [1, 2, 3], "b": "key1": "val1", "key2": {"key3":"val3"} }, "c": 3, "d" : 1, "e": "xyz", "f": true, "g" : null}'; +SELECT JSON_OBJECT_TO_ARRAY(@obj1); +JSON_OBJECT_TO_ARRAY(@obj1) +NULL +Warnings: +Warning 4038 Syntax error in JSON text in argument 1 to function 'json_object_to_array' at position 30 +Checking incorrect type in argument +SET @arr1= '[1, 2, 3]'; +SELECT JSON_OBJECT_TO_ARRAY(@arr1); +JSON_OBJECT_TO_ARRAY(@arr1) +NULL +# +# End of 11.2 Test +# diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index 4f7c1eaebcd..665375d94bf 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -3783,3 +3783,162 @@ FROM JSON_TABLE( COLUMNS (k VARCHAR(20) PATH '$.key', v VARCHAR(20) PATH '$.value', id FOR ORDINALITY)) AS jt; --echo # End of 11.2 test + +--echo # +--echo # Beginning of 11.2 tests +--echo # +--echo # MDEV-26182: Implement json_array_intersect() +--echo # + + + +--echo # JSON_ARRAY_INTERSECT() + +--echo # Scalar as elements + +SET @json1= '[1,2,3]'; +SET @json2= '[1,2,3]'; +SELECT json_array_intersect(@json1, @json2); + +SET @json1= '[1,2,3]'; +SET @json2= '[1,2,4]'; +SELECT json_array_intersect(@json1, @json2); + +SET @json1= '["abc","def","ghi"]'; +SET @json2= '["xyz", "abc", "tuv"]'; +SELECT json_array_intersect(@json1, @json2); + +SET @obj1= '[true]'; +SET @obj2= '[false, "true"]'; +select json_array_intersect(@obj1, @obj2); + +SET @obj1= '[true]'; +SET @obj2= '[false, true]'; +select json_array_intersect(@obj1, @obj2); + +SET @obj1= '[null, true]'; +SET @obj2= '[false, null]'; +select json_array_intersect(@obj1, @obj2); + + +--echo # array as elements + +SET @json1= '[6,6,6]'; +SET @json2= '[[1,2,3],[4,5,6],[1,3,2]]'; +SELECT json_array_intersect(@json1, @json2); + +SET @json1= '[[1,2,3],[4,5,6],[1,3,2]]'; +SET @json2= '[[1,2,3],[4,5,6],[1,3,2]]'; +SELECT json_array_intersect(@json1, @json2); + +SET @json1= '[[1,2,3],[4,5,6],[1,3,2]]'; +SET @json2= '[[1,2,3],[4,5,6],[1,4,2]]'; +SELECT json_array_intersect(@json1, @json2); + +--echo # object as elements + +SET @json1 = '[{"k1":"v1","k2":"v2"},{"k2":"v2"}]'; +SET @json2 = '[{"kkey1":"vvalue1"},{"k2":"v2","k1":"v1"}]'; +SELECT json_array_intersect(@json1, @json2); + +SET @json1 = '[{"k1":"v1","k2":"v2"},{"k2":"v2","k1":"v1"}]'; +SET @json2 = '[{"k1":"v1","k2":"v2"},{"k1":"v1","k2":"v2"}]'; +SELECT json_array_intersect(@json1, @json2); + +--echo # multi type elements + +SET @obj1= '[1,2,3, "abc", "xyz", {"key1":"val1"}, {"key2":"val2"}, [1,2,3]]'; +SET @obj2= '[3.0, 4, 5, "abc", {"key1":"val1"}, [3,2,1]]'; +select json_array_intersect(@obj1, @obj2); + +SET @obj1= '[1, 2, 3, "abc", "xyz", {"key1": {"key2" : [1,2,3] } }, [4,5,6] ]'; +SET @obj2= '[3.0, 4, 5, "abc", {"key1": {"key2" : [3,2,1]} }, {"key1": {"key2" : [1,2,3] } }, [4,5,6], [6,5,4] ]'; +select json_array_intersect(@obj1, @obj2); + +--echo # Checking duplicates + +SET @obj1= '[1, 2, 3, 3, 3.0, "abc", true, true, {"key1":"val1"}]'; +SET @obj2= '[3.0, 3, 5, "abc", "abc", true, {"key2":"val2"}, {"key1":"val1"}, {"key1":"val2"}]'; +select json_array_intersect(@obj1, @obj2); + + +--echo # Checking Syntax error for JSON_ARRAY_INTERSECT() +SET @obj1= '[1, 2, 3, 3, 3.0, "abc", true, true, {"key1":"val1" ]'; +SET @obj2= '[3.0, 3, 5, "abc", "abc", true, {"key2":"val2"}, {"key1":"val1"}, {"key1":"val2"}]'; +select json_array_intersect(@obj1, @obj2); + +--echo # Checking incorrect type for input +SET @obj1= '{"key1": "val1"}'; +SET @arr1= '[ 1, 2, 3 ]'; +SET @num1= '2'; +SET @str1= '"abc"'; +SET @bool1= 'true'; +select json_array_intersect(@obj1, @arr1); +select json_array_intersect(@arr1, @obj1); +select json_array_intersect(@arr1, @num1); +select json_array_intersect(@num1, @bool1); + + + +--echo # JSON_OBJECT_FILTER_KEYS() + +SET @obj1= '{ "a": 1, "b": 2, "c": 3}'; +SET @obj2= '{"b" : 10, "c": 20, "d": 30}'; +SELECT JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))); + +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @obj2= '{"b" : 10, "c": 20, "d": 30}'; +SELECT JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))); + + +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @arr2='["x", "y", "z"]'; +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @arr2); + + +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @arr2='["key2", "key1", "b"]'; +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @arr1); + +--echo # Incorrect type in input returns NULL + +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @obj1); +SELECT JSON_OBJECT_FILTER_KEYS(@arr1, @arr1); + +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @scalar1='2'; +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @scalar1); + +--echo # Checking syntax error + +SET @obj1= '{ "a": 1, "b": 2, "c": 3}'; +SET @obj2= '{"b" : 10, "c": 20, "d" 30}'; +SELECT JSON_OBJECT_FILTER_KEYS (@obj1, json_array_intersect(json_keys(@obj1), json_keys(@obj2))); + +SET @obj1= '{ "a": 1, "b": {"key1": {"key2":"val2"}}, "c": [1, 2, 3] }'; +SET @arr2= '[ "key2", "key1", "b" '; +SELECT JSON_OBJECT_FILTER_KEYS(@obj1, @arr1); + + + +--echo # JSON_OBJECT_TO_ARRAY() + +SET @obj1= '{ "a": [1, 2, 3], "b": { "key1":"val1", "key2": {"key3":"val3"} }, "c": 3, "d" : 1, "e": "xyz", "f": true, "g" : null}'; +SELECT JSON_OBJECT_TO_ARRAY(@obj1); + +SET @obj1= '{ "a": [1, 2, 3], "b": { "key1":"val1", "key2": [1, 2, 3] }, "c": 3, "d" : 1, "e": "xyz", "f": true, "g" : null}'; +SELECT JSON_OBJECT_TO_ARRAY(@obj1); + +--echo # Checking syntax error + +SET @obj1= '{ "a": [1, 2, 3], "b": "key1": "val1", "key2": {"key3":"val3"} }, "c": 3, "d" : 1, "e": "xyz", "f": true, "g" : null}'; +SELECT JSON_OBJECT_TO_ARRAY(@obj1); + +--echo Checking incorrect type in argument + +SET @arr1= '[1, 2, 3]'; +SELECT JSON_OBJECT_TO_ARRAY(@arr1); + +--echo # +--echo # End of 11.2 Test +--echo # diff --git a/sql/item_create.cc b/sql/item_create.cc index 0a26285f135..de3fb8aed6f 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -958,6 +958,17 @@ protected: virtual ~Create_func_json_normalize() = default; }; +class Create_func_json_object_to_array : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_json_object_to_array s_singleton; + +protected: + Create_func_json_object_to_array() {} + virtual ~Create_func_json_object_to_array() {} +}; class Create_func_json_equals : public Create_func_arg2 { @@ -1377,6 +1388,32 @@ protected: }; +class Create_func_json_array_intersect : public Create_func_arg2 +{ +public: + virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2); + + static Create_func_json_array_intersect s_singleton; + +protected: + Create_func_json_array_intersect() {} + virtual ~Create_func_json_array_intersect() {} +}; + + +class Create_func_json_object_filter_keys : public Create_func_arg2 +{ +public: + virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2); + + static Create_func_json_object_filter_keys s_singleton; + +protected: + Create_func_json_object_filter_keys() {} + virtual ~Create_func_json_object_filter_keys() {} +}; + + class Create_func_last_day : public Create_func_arg1 { public: @@ -3818,6 +3855,15 @@ Create_func_json_normalize::create_1_arg(THD *thd, Item *arg1) return new (thd->mem_root) Item_func_json_normalize(thd, arg1); } +Create_func_json_object_to_array Create_func_json_object_to_array::s_singleton; + +Item* +Create_func_json_object_to_array::create_1_arg(THD *thd, Item *arg1) +{ + status_var_increment(thd->status_var.feature_json); + return new (thd->mem_root) Item_func_json_object_to_array(thd, arg1); +} + Create_func_json_equals Create_func_json_equals::s_singleton; @@ -4204,6 +4250,27 @@ Create_func_json_length::create_native(THD *thd, const LEX_CSTRING *name, return func; } +Create_func_json_array_intersect Create_func_json_array_intersect::s_singleton; +Item* +Create_func_json_array_intersect::create_2_arg(THD *thd, Item *arg1, Item *arg2) +{ + if (unlikely( ( !arg1 || !arg2 ) )) // json, json + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0)); + } + status_var_increment(thd->status_var.feature_json); + return new (thd->mem_root) Item_func_json_array_intersect(thd, arg1, arg2); +} + +Create_func_json_object_filter_keys Create_func_json_object_filter_keys::s_singleton; + +Item* +Create_func_json_object_filter_keys::create_2_arg(THD *thd, Item *arg1, Item *arg2) +{ + status_var_increment(thd->status_var.feature_json); + return new (thd->mem_root) Item_func_json_object_filter_keys(thd, arg1, arg2); +} + Create_func_json_merge Create_func_json_merge::s_singleton; @@ -5822,6 +5889,7 @@ Native_func_registry func_array[] = { { STRING_WITH_LEN("JSON_ARRAY") }, BUILDER(Create_func_json_array)}, { { STRING_WITH_LEN("JSON_ARRAY_APPEND") }, BUILDER(Create_func_json_array_append)}, { { STRING_WITH_LEN("JSON_ARRAY_INSERT") }, BUILDER(Create_func_json_array_insert)}, + { { STRING_WITH_LEN("JSON_ARRAY_INTERSECT") }, BUILDER(Create_func_json_array_intersect)}, { { STRING_WITH_LEN("JSON_COMPACT") }, BUILDER(Create_func_json_compact)}, { { STRING_WITH_LEN("JSON_CONTAINS") }, BUILDER(Create_func_json_contains)}, { { STRING_WITH_LEN("JSON_CONTAINS_PATH") }, BUILDER(Create_func_json_contains_path)}, @@ -5843,6 +5911,8 @@ Native_func_registry func_array[] = { { STRING_WITH_LEN("JSON_QUERY") }, BUILDER(Create_func_json_query)}, { { STRING_WITH_LEN("JSON_QUOTE") }, BUILDER(Create_func_json_quote)}, { { STRING_WITH_LEN("JSON_OBJECT") }, BUILDER(Create_func_json_object)}, + { { STRING_WITH_LEN("JSON_OBJECT_FILTER_KEYS") }, BUILDER(Create_func_json_object_filter_keys)}, + { { STRING_WITH_LEN("JSON_OBJECT_TO_ARRAY") }, BUILDER(Create_func_json_object_to_array)}, { { STRING_WITH_LEN("JSON_OVERLAPS") }, BUILDER(Create_func_json_overlaps)}, { { STRING_WITH_LEN("JSON_REMOVE") }, BUILDER(Create_func_json_remove)}, { { STRING_WITH_LEN("JSON_REPLACE") }, BUILDER(Create_func_json_replace)}, diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 736e85cd691..a66e990aaf2 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -4982,3 +4982,515 @@ bool Item_func_json_key_value::fix_length_and_dec(THD *thd) return FALSE; } + + +bool create_hash(json_engine_t *value, HASH *items, bool &hash_inited, + MEM_ROOT *hash_root) +{ + int level= value->stack_p; + if (my_hash_init(PSI_INSTRUMENT_ME, items, value->s.cs, 0, 0, 0, + (my_hash_get_key) get_key_name, NULL, 0)) + return true; + hash_inited= true; + + while (json_scan_next(value) == 0 && value->stack_p >= level) + { + const uchar *value_start= NULL; + size_t value_len= 0; + DYNAMIC_STRING norm_val; + + if (json_read_value(value) || + get_current_value(value, value_start, value_len) || + init_dynamic_string(&norm_val, NULL, 0, 0)) + return true; + + if (json_normalize(&norm_val, (const char*) value_start, + value_len, value->s.cs)) + { + dynstr_free(&norm_val); + return true; + } + + char *new_entry= (char*)alloc_root(hash_root, + norm_val.length+1); + if (!new_entry) + { + dynstr_free(&norm_val); + return true; + } + else + { + strncpy(new_entry, norm_val.str, norm_val.length); + new_entry[norm_val.length]='\0'; + + dynstr_free(&norm_val); + + if (my_hash_insert(items, (const uchar *) new_entry)) + { + my_free(new_entry); + return true; + } + } + } + return false; +} + + +/* + Get the starting pointer and length of the value of the current layer. + RETURN + FALSE - The function was successfully completed without errors. + TRUE - An error occurred while running. +*/ +bool get_current_value(json_engine_t *js, const uchar *&value_start, + size_t &value_len) +{ + value_start= js->value_begin; + + if (json_value_scalar(js)) + { + value_len= js->value_end - value_start; + } + else + { + if (json_skip_level(js)) + return true; + value_len= js->s.c_str - value_start; + } + return false; +} + + +/* + If the outermost layer of JSON is an array, + the intersection of arrays is independent of order. + Create a hash containing all elements in the array, + itterate over another array and add the common elements + to the result. + + RETURN + FALSE - if two array documents have intersection + TRUE - If two array documents do not have intersection +*/ +bool get_intersect_between_arrays(String *str, json_engine_t *value, + HASH items) +{ + bool res= true, has_value= false; + int level= value->stack_p; + String temp_str(0); + + temp_str.append('['); + while (json_scan_next(value) == 0 && value->stack_p >= level) + { + const uchar *value_start= NULL; + size_t value_len= 0; + DYNAMIC_STRING norm_val; + + if (json_read_value(value) || + get_current_value(value, value_start, value_len) || + init_dynamic_string(&norm_val, NULL, 0, 0)) + goto error; + + if (json_normalize(&norm_val, (const char*) value_start, + value_len, value->s.cs)) + { + dynstr_free(&norm_val); + goto error; + } + + char *new_entry= (char*)malloc(norm_val.length+1); + if (!new_entry) + { + dynstr_free(&norm_val); + goto error; + } + strncpy(new_entry, norm_val.str, norm_val.length); + new_entry[norm_val.length]='\0'; + + dynstr_free(&norm_val); + + /* + If the same value is found in the hash table, add + that value to str. At the same time, update the number + of times the value appears in the hash table. + */ + uchar * found= NULL; + if ((found= my_hash_search(&items, + (const uchar *) new_entry, + strlen(new_entry)))) + { + has_value= true; + temp_str.append( (const char*) value_start, value_len); + temp_str.append(','); + if (my_hash_delete(&items, found)) + { + free(new_entry); + goto error; + } + } + free(new_entry); + } + + res= false; + + if (has_value) + { + temp_str.chop(); /* remove last comma because there are no values after that. */ + temp_str.append(']'); + str->append(temp_str.ptr(), temp_str.length()); + } + +error: + return res; +} + + +String* Item_func_json_array_intersect::val_str(String *str) +{ + DBUG_ASSERT(fixed()); + + json_engine_t je2, res_je; + String *js2= args[1]->val_json(&tmp_js2); + + if (null_value || args[1]->null_value) + goto null_return; + + str->set_charset(js2->charset()); + str->length(0); + + json_scan_start(&je2, js2->charset(), (const uchar *) js2->ptr(), + (const uchar *) js2->ptr() + js2->length()); + + if (json_read_value(&je2) || je2.value_type != JSON_VALUE_ARRAY) + goto error_return; + + if (get_intersect_between_arrays(str, &je2, items)) + goto error_return; + + if (str->length()) + { + json_scan_start(&res_je, str->charset(), (const uchar *) str->ptr(), + (const uchar *) str->ptr() + str->length()); + str= &tmp_js1; + if (json_nice(&res_je, str, Item_func_json_format::LOOSE)) + goto error_return; + + null_value= 0; + return str; + } + else + { + goto null_return; + } + +error_return: + if (je2.s.error) + report_json_error(js2, &je2, 1); +null_return: + null_value= 1; + return NULL; +} + +bool Item_func_json_array_intersect::fix_length_and_dec(THD *thd) +{ + json_engine_t je1; + String *js1= args[0]->val_json(&tmp_js1); + + if (args[0]->null_value) + { + null_value= true; + return FALSE; + } + json_scan_start(&je1, js1->charset(), (const uchar *) js1->ptr(), + (const uchar *) js1->ptr() + js1->length()); + /* + Scan value uses the hash table to get the intersection of two arrays. + */ + + init_alloc_root(PSI_NOT_INSTRUMENTED, &hash_root, 1024, 0, MYF(0)); + root_inited= true; + + if (json_read_value(&je1) || je1.value_type != JSON_VALUE_ARRAY || + create_hash(&je1, &items, hash_inited, &hash_root)) + { + if (je1.s.error) + report_json_error(js1, &je1, 0); + null_value= 1; + return FALSE; + } + + max_length= (args[0]->max_length < args[1]->max_length) ? + args[0]->max_length : args[1]->max_length; + + set_maybe_null(); + return FALSE; +} + + +bool filter_keys(json_engine_t *je1, String *str, HASH items) +{ + int level= je1->stack_p; + String temp_str(0); + bool res= true, has_value= false; + + temp_str.append('{'); + while (json_scan_next(je1)==0 && level <= je1->stack_p) + { + switch(je1->state) + { + case JST_KEY: + { + const uchar *key_start= je1->s.c_str; + const uchar *key_end; + String str("", 0, je1->s.cs); + str.append('"'); + + do + { + key_end= je1->s.c_str; + } while (json_read_keyname_chr(je1) == 0); + + if (unlikely(je1->s.error)) + goto error; + if (json_read_value(je1)) + goto error; + + const uchar* value_start= NULL; + size_t value_len= 0; + if (get_current_value(je1, value_start, value_len)) + goto error; + + str.append((const char*)key_start, (size_t)(key_end-key_start)); + str.append('"'); + str.append('\0'); + + char *curr_key= (char*)malloc((size_t)(key_end-key_start+3)); + strncpy(curr_key, str.ptr(), str.length()); + + if (my_hash_search(&items, (const uchar*)curr_key, strlen(curr_key))) + { + has_value= true; + + temp_str.append('"'); + temp_str.append((const char*)key_start, (size_t)(key_end-key_start)); + temp_str.append('"'); + + temp_str.append(':'); + + temp_str.append((const char*)value_start, value_len); + + temp_str.append(','); + } + free(curr_key); + } + } + } + + res= false; + + if (has_value) + { + temp_str.chop(); + temp_str.append('}'); + str->append(temp_str.ptr(), temp_str.length()); + } + +error: + return res; +} + +String* Item_func_json_object_filter_keys::val_str(String *str) +{ + DBUG_ASSERT(fixed()); + + json_engine_t je1, res_je; + String *js1= args[0]->val_json(&tmp_js1); + + if (null_value || args[0]->null_value) + goto null_return; + + str->set_charset(js1->charset()); + str->length(0); + + json_scan_start(&je1, js1->charset(),(const uchar *) js1->ptr(), + (const uchar *) js1->ptr() + js1->length()); + + if (json_read_value(&je1) || je1.value_type != JSON_VALUE_OBJECT) + goto error_return; + + if(filter_keys(&je1, str, items)) + goto null_return; + + if (str->length()) + { + json_scan_start(&res_je, str->charset(), (const uchar *) str->ptr(), + (const uchar *) str->ptr() + str->length()); + str= &tmp_js1; + if (json_nice(&res_je, str, Item_func_json_format::LOOSE)) + goto error_return; + + null_value= 0; + return str; + } + else + { + goto null_return; + } + + +error_return: + if (je1.s.error) + report_json_error(js1, &je1, 0); +null_return: + null_value= 1; + return NULL; +} + + +bool Item_func_json_object_filter_keys::fix_length_and_dec(THD *thd) +{ + String *js2= args[1]->val_json(&tmp_js2); + json_engine_t je2; + + if (args[1]->null_value) + { + null_value= 1; + return FALSE; + } + + json_scan_start(&je2, js2->charset(),(const uchar *) js2->ptr(), + (const uchar *) js2->ptr() + js2->length()); + init_alloc_root(PSI_NOT_INSTRUMENTED, &hash_root, 1024, 0, MYF(0)); + root_inited= true; + + if (json_read_value(&je2) || je2.value_type != JSON_VALUE_ARRAY || + create_hash(&je2, &items, hash_inited, &hash_root)) + { + if (je2.s.error) + report_json_error(js2, &je2, 0); + null_value= 1; + return FALSE; + } + + max_length= args[0]->max_length; + set_maybe_null(); + + return FALSE; +} + +bool convert_to_array(json_engine_t *je, String *str) +{ + int level= je->stack_p; + String temp_str(0); + temp_str.append('['); + + while (json_scan_next(je)==0 && level <= je->stack_p) + { + switch(je->state) + { + case JST_KEY: + { + temp_str.append('['); + + const uchar *key_start= je->s.c_str; + const uchar *key_end; + + do + { + key_end= je->s.c_str; + } while (json_read_keyname_chr(je) == 0); + + if (unlikely(je->s.error)) + return true; + + temp_str.append('"'); + temp_str.append((const char*)key_start, (size_t)(key_end-key_start)); + temp_str.append('"'); + + temp_str.append(','); + + int v_len= 0; + const uchar *value= NULL; + + if (json_read_value(je)) + return true; + value= je->value_begin; + if (json_value_scalar(je)) + v_len= (int)(je->value_end - value); + else + { + if (json_skip_level(je)) + return true; + v_len= (int)(je->s.c_str - value); + } + temp_str.append((const char *) value, v_len); + + temp_str.append(']'); + temp_str.append(','); + } + } + } + if (je->s.error) + return true; + + temp_str.chop(); /* remove the last comma. */ + temp_str.append(']'); + str->append(temp_str.ptr(), temp_str.length()); + return false; +} + +String* Item_func_json_object_to_array::val_str(String *str) +{ + DBUG_ASSERT(fixed()); + + json_engine_t je; + String *js1= args[0]->val_str(&tmp); + + if (args[0]->null_value) + goto null_return; + + str->set_charset(js1->charset()); + str->length(0); + + json_scan_start(&je, js1->charset(),(const uchar *) js1->ptr(), + (const uchar *) js1->ptr() + js1->length()); + + if (json_read_value(&je)) + goto error_return; + if (je.value_type != JSON_VALUE_OBJECT) + goto null_return; + + if (convert_to_array(&je, str)) + goto error_return; + + if (str->length()) + { + json_scan_start(&je, str->charset(), (const uchar *) str->ptr(), + (const uchar *) str->ptr() + str->length()); + str= &tmp; + if (json_nice(&je, str, Item_func_json_format::LOOSE)) + goto error_return; + + null_value= 0; + return str; + } + else + { + goto null_return; + } + +error_return: + if (je.s.error) + report_json_error(js1, &je, 0); +null_return: + null_value= 1; + return NULL; +} + + +bool Item_func_json_object_to_array::fix_length_and_dec(THD *thd) +{ + max_length= args[0]->max_length + (args[0]->max_length/2); + set_maybe_null(); + return FALSE; +} diff --git a/sql/item_jsonfunc.h b/sql/item_jsonfunc.h index a9bd01a386e..30f2f38827f 100644 --- a/sql/item_jsonfunc.h +++ b/sql/item_jsonfunc.h @@ -60,7 +60,13 @@ int json_find_overlap_with_array(json_engine_t *js, json_engine_t *value, bool compare_whole); - +bool create_hash(json_engine_t *value, HASH *items, bool &hash_inited); +bool create_item(LEX_CSTRING *&new_entry, + const uchar *value_start, size_t value_len); +bool get_current_value(json_engine_t *js, const uchar *&value_start, + size_t &value_len); +bool get_intersect_between_arrays(String *str, + json_engine_t *value); class Json_engine_scan: public json_engine_t { @@ -491,6 +497,25 @@ public: }; +class Item_func_json_object_to_array: public Item_json_func +{ + protected: + String tmp; +public: + Item_func_json_object_to_array(THD *thd, Item *a): + Item_json_func(thd, a) {} + String *val_str(String *) override; + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("json_object_to_array") }; + return name; + } + bool fix_length_and_dec(THD *thd) override; + Item *get_copy(THD *thd) override + { return get_item_copy(thd, this); } +}; + + class Item_func_json_length: public Item_long_func { bool check_arguments() const override @@ -855,4 +880,69 @@ public: }; + +class Item_func_json_array_intersect: public Item_str_func +{ +protected: + String tmp_js1, tmp_js2; + bool hash_inited, root_inited; + HASH items; + MEM_ROOT hash_root; +public: + Item_func_json_array_intersect(THD *thd, Item *a, Item *b): + Item_str_func(thd, a, b) { hash_inited= root_inited= false; } + String *val_str(String *) override; + bool fix_length_and_dec(THD *thd) override; + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("json_array_intersect") }; + return name; + } + Item *get_copy(THD *thd) override + { return get_item_copy(thd, this); } + void cleanup() override + { + if (hash_inited) + my_hash_free(&items); + } + ~Item_func_json_array_intersect() + { + if (root_inited) + free_root(&hash_root, MYF(0)); + } +}; + +class Item_func_json_object_filter_keys: public Item_str_func +{ +protected: + String tmp_js1, tmp_js2; + bool hash_inited, root_inited; + HASH items; + MEM_ROOT hash_root; +public: + Item_func_json_object_filter_keys(THD *thd, Item *a, Item *b): + Item_str_func(thd, a, b) { hash_inited= root_inited= false; } + String *val_str(String *) override; + bool fix_length_and_dec(THD *thd) override; + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("json_object_filter_keys") }; + return name; + } + Item *get_copy(THD *thd) override + { return get_item_copy(thd, this); } + + void cleanup() override + { + if (hash_inited) + my_hash_free(&items); + } + ~Item_func_json_object_filter_keys() + { + if (root_inited) + free_root(&hash_root, MYF(0)); + } +}; + + #endif /* ITEM_JSONFUNC_INCLUDED */