diff options
-rw-r--r-- | include/json_lib.h | 7 | ||||
-rw-r--r-- | mysql-test/r/dyncol.result | 32 | ||||
-rw-r--r-- | mysql-test/r/func_json.result | 93 | ||||
-rw-r--r-- | mysql-test/t/dyncol.test | 16 | ||||
-rw-r--r-- | mysql-test/t/func_json.test | 43 | ||||
-rw-r--r-- | sql/item.h | 12 | ||||
-rw-r--r-- | sql/item_cmpfunc.cc | 42 | ||||
-rw-r--r-- | sql/item_create.cc | 302 | ||||
-rw-r--r-- | sql/item_func.h | 2 | ||||
-rw-r--r-- | sql/item_jsonfunc.cc | 985 | ||||
-rw-r--r-- | sql/item_jsonfunc.h | 129 | ||||
-rw-r--r-- | sql/lex.h | 1 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 7 | ||||
-rw-r--r-- | storage/connect/mysql-test/connect/disabled.def | 1 | ||||
-rw-r--r-- | strings/json_lib.c | 6 |
15 files changed, 1619 insertions, 59 deletions
diff --git a/include/json_lib.h b/include/json_lib.h index d9e3bb7c624..ecd108c7567 100644 --- a/include/json_lib.h +++ b/include/json_lib.h @@ -227,6 +227,13 @@ int json_read_keyname_chr(json_engine_t *j); /* + Check if the name of the current JSON key matches + the step of the path. +*/ +int json_key_matches(json_engine_t *je, json_string_t *k); + + +/* json_read_value() function parses the JSON value syntax, so that we can handle the value of a key or an array item. It only returns meaningful result when the engine is in diff --git a/mysql-test/r/dyncol.result b/mysql-test/r/dyncol.result index 105123e2f1e..5b3ab44c19f 100644 --- a/mysql-test/r/dyncol.result +++ b/mysql-test/r/dyncol.result @@ -1731,36 +1731,36 @@ column_json(column_create(1, "val", 2, column_create(3, "val2"))) # Time encoding # select hex(column_create("t", "800:46:06.23434" AS time)) as hex, -column_json(column_create("t", "800:46:06.23434" AS time)) as json; -hex json +column_json(column_create("t", "800:46:06.23434" AS time)) as js; +hex js 04010001000000070074649363B82003 {"t":"800:46:06.234340"} select hex(column_create(1, "800:46:06.23434" AS time)) as hex, -column_json(column_create(1, "800:46:06.23434" AS time)) as json; -hex json +column_json(column_create(1, "800:46:06.23434" AS time)) as js; +hex js 000100010007649363B82003 {"1":"800:46:06.234340"} select hex(column_create("t", "800:46:06" AS time)) as hex, -column_json(column_create("t", "800:46:06" AS time)) as json; -hex json +column_json(column_create("t", "800:46:06" AS time)) as js; +hex js 04010001000000070074860B32 {"t":"800:46:06"} select hex(column_create(1, "800:46:06" AS time)) as hex, -column_json(column_create(1, "800:46:06" AS time)) as json; -hex json +column_json(column_create(1, "800:46:06" AS time)) as js; +hex js 000100010007000060B82003 {"1":"800:46:06"} select hex(column_create("t", "2012-12-21 10:46:06.23434" AS datetime)) as hex, -column_json(column_create("t", "2012-12-21 10:46:06.23434" AS datetime)) as json; -hex json +column_json(column_create("t", "2012-12-21 10:46:06.23434" AS datetime)) as js; +hex js 0401000100000005007495B90F649363B80A00 {"t":"2012-12-21 10:46:06.234340"} select hex(column_create(1, "2012-12-21 10:46:06.23434" AS datetime)) as hex, -column_json(column_create(1, "2012-12-21 10:46:06.23434" AS datetime)) as json; -hex json +column_json(column_create(1, "2012-12-21 10:46:06.23434" AS datetime)) as js; +hex js 00010001000595B90F649363B80A00 {"1":"2012-12-21 10:46:06.234340"} select hex(column_create("t", "2012-12-21 10:46:06" AS datetime)) as hex, -column_json(column_create("t", "2012-12-21 10:46:06" AS datetime)) as json; -hex json +column_json(column_create("t", "2012-12-21 10:46:06" AS datetime)) as js; +hex js 0401000100000005007495B90F86AB00 {"t":"2012-12-21 10:46:06"} select hex(column_create(1, "2012-12-21 10:46:06" AS datetime)) as hex, -column_json(column_create(1, "2012-12-21 10:46:06" AS datetime)) as json; -hex json +column_json(column_create(1, "2012-12-21 10:46:06" AS datetime)) as js; +hex js 00010001000595B90F000060B80A00 {"1":"2012-12-21 10:46:06"} # # MDEV-4849: Out of memory error and valgrind warnings on COLUMN_ADD diff --git a/mysql-test/r/func_json.result b/mysql-test/r/func_json.result index 1a47dd79fe8..6ee49d68787 100644 --- a/mysql-test/r/func_json.result +++ b/mysql-test/r/func_json.result @@ -46,6 +46,18 @@ json_array_append('["a", "b"]', '$', FALSE) select json_array_append('{"k1":1, "k2":["a", "b"]}', '$.k2', 2); json_array_append('{"k1":1, "k2":["a", "b"]}', '$.k2', 2) {"k1":1, "k2":["a", "b", 2]} +SELECT JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[1]', 'x'); +JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[1]', 'x') +["a", "x", {"b": [1, 2]}, [3, 4]] +SELECT JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[2]', 'x'); +JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[2]', 'x') +["a", {"b": [1, 2]}, "x", [3, 4]] +SELECT JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[3]', 'x'); +JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[3]', 'x') +["a", {"b": [1, 2]}, [3, 4], "x"] +SELECT JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[4]', 'x'); +JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[4]', 'x') +["a", {"b": [1, 2]}, [3, 4], "x"] select json_contains('{"k1":123, "k2":345}', '123', '$.k1'); json_contains('{"k1":123, "k2":345}', '123', '$.k1') 1 @@ -91,6 +103,44 @@ json_extract('{"key1":5, "key2":[2,3]}', "$.key1", "$.key2") select json_extract('{"key0":true, "key1":"qwe"}', "$.key1"); json_extract('{"key0":true, "key1":"qwe"}', "$.key1") qwe +select json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.b.k1', 'word'); +json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.b.k1', 'word') +{"a":1, "b":{"c":1, "k1":"word"}, "d":[1, 2]} +select json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.d[3]', 3); +json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.d[3]', 3) +{"a":1, "b":{"c":1}, "d":[1, 2, 3]} +select json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.a[2]', 2); +json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.a[2]', 2) +{"a":[1, 2], "b":{"c":1}, "d":[1, 2]} +select json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.b.c', 'word'); +json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.b.c', 'word') +{"a":1, "b":{"c":1}, "d":[1, 2]} +select json_set('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.c', '[true, false]'); +json_set('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.c', '[true, false]') +{ "a": 10, "b": [2, 3], "c":"[true, false]"} +select json_replace('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.c', '[true, false]'); +json_replace('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.c', '[true, false]') +{ "a": 10, "b": [2, 3]} +select json_replace('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.b', '[true, false]'); +json_replace('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.b', '[true, false]') +{ "a": 10, "b": "[true, false]"} +set @j = '["a", ["b", "c"], "d"]'; +select json_remove(@j, '$[0]'); +json_remove(@j, '$[0]') +[ ["b", "c"], "d"] +select json_remove(@j, '$[1]'); +json_remove(@j, '$[1]') +["a" "d"] +select json_remove(@j, '$[2]'); +json_remove(@j, '$[2]') +["a", ["b", "c"]] +set @j = '{"a": 1, "b": [2, 3]}'; +select json_remove(@j, '$.b'); +json_remove(@j, '$.b') +{"a": 1} +select json_remove(@j, '$.a'); +json_remove(@j, '$.a') +{"b": [2, 3]} select json_object("ki", 1, "mi", "ya"); json_object("ki", 1, "mi", "ya") {"ki": 1, "mi": "ya"} @@ -121,3 +171,46 @@ BOOLEAN select json_type('123'); json_type('123') NUMBER +select json_keys('{"a":{"c":1, "d":2}, "b":2}'); +json_keys('{"a":{"c":1, "d":2}, "b":2}') +["a", "b"] +select json_keys('{"a":{"c":1, "d":2}, "b":2}', "$.a"); +json_keys('{"a":{"c":1, "d":2}, "b":2}', "$.a") +["c", "d"] +select json_keys('{"a":{"c":1, "d":2}, "b":2}', "$.b"); +json_keys('{"a":{"c":1, "d":2}, "b":2}', "$.b") +NULL +SET @j = '["abc", [{"k": "10"}, "def"], {"x":"abc"}, {"y":"bcd"}]'; +select json_search(@j, 'one', 'abc'); +json_search(@j, 'one', 'abc') +"$[0]" +select json_search(@j, 'all', 'abc'); +json_search(@j, 'all', 'abc') +["$[0]", "$[2].x"] +select json_search(@j, 'all', 'abc', NULL, '$[2]'); +json_search(@j, 'all', 'abc', NULL, '$[2]') +"$[2].x" +select json_search(@j, 'all', 'abc', NULL, '$'); +json_search(@j, 'all', 'abc', NULL, '$') +["$[0]", "$[2].x"] +select json_search(@j, 'all', '10', NULL, '$'); +json_search(@j, 'all', '10', NULL, '$') +"$[1][0].k" +select json_search(@j, 'all', '10', NULL, '$[*]'); +json_search(@j, 'all', '10', NULL, '$[*]') +"$[1][0].k" +select json_search(@j, 'all', '10', NULL, '$[*][0].k'); +json_search(@j, 'all', '10', NULL, '$[*][0].k') +"$[1][0].k" +select json_unquote('"abc"'); +json_unquote('"abc"') +abc +select json_object("a", json_object("b", "abcd")); +json_object("a", json_object("b", "abcd")) +{"a": {"b": "abcd"}} +select json_object("a", '{"b": "abcd"}'); +json_object("a", '{"b": "abcd"}') +{"a": "{\"b\": \"abcd\"}"} +select json_object("a", cast('{"b": "abcd"}' as json)); +json_object("a", cast('{"b": "abcd"}' as json)) +{"a": {"b": "abcd"}} diff --git a/mysql-test/t/dyncol.test b/mysql-test/t/dyncol.test index 49b2c5542d3..f8f198be445 100644 --- a/mysql-test/t/dyncol.test +++ b/mysql-test/t/dyncol.test @@ -805,24 +805,24 @@ select column_json(column_create(1, "val", 2, column_create(3, "val2"))); --echo # Time encoding --echo # select hex(column_create("t", "800:46:06.23434" AS time)) as hex, - column_json(column_create("t", "800:46:06.23434" AS time)) as json; + column_json(column_create("t", "800:46:06.23434" AS time)) as js; select hex(column_create(1, "800:46:06.23434" AS time)) as hex, - column_json(column_create(1, "800:46:06.23434" AS time)) as json; + column_json(column_create(1, "800:46:06.23434" AS time)) as js; select hex(column_create("t", "800:46:06" AS time)) as hex, - column_json(column_create("t", "800:46:06" AS time)) as json; + column_json(column_create("t", "800:46:06" AS time)) as js; select hex(column_create(1, "800:46:06" AS time)) as hex, - column_json(column_create(1, "800:46:06" AS time)) as json; + column_json(column_create(1, "800:46:06" AS time)) as js; select hex(column_create("t", "2012-12-21 10:46:06.23434" AS datetime)) as hex, - column_json(column_create("t", "2012-12-21 10:46:06.23434" AS datetime)) as json; + column_json(column_create("t", "2012-12-21 10:46:06.23434" AS datetime)) as js; select hex(column_create(1, "2012-12-21 10:46:06.23434" AS datetime)) as hex, - column_json(column_create(1, "2012-12-21 10:46:06.23434" AS datetime)) as json; + column_json(column_create(1, "2012-12-21 10:46:06.23434" AS datetime)) as js; select hex(column_create("t", "2012-12-21 10:46:06" AS datetime)) as hex, - column_json(column_create("t", "2012-12-21 10:46:06" AS datetime)) as json; + column_json(column_create("t", "2012-12-21 10:46:06" AS datetime)) as js; select hex(column_create(1, "2012-12-21 10:46:06" AS datetime)) as hex, - column_json(column_create(1, "2012-12-21 10:46:06" AS datetime)) as json; + column_json(column_create(1, "2012-12-21 10:46:06" AS datetime)) as js; --echo # --echo # MDEV-4849: Out of memory error and valgrind warnings on COLUMN_ADD diff --git a/mysql-test/t/func_json.test b/mysql-test/t/func_json.test index 3990ff24fc4..ffe48b0f9db 100644 --- a/mysql-test/t/func_json.test +++ b/mysql-test/t/func_json.test @@ -19,6 +19,11 @@ select json_array(1, "text", false, null); select json_array_append('["a", "b"]', '$', FALSE); select json_array_append('{"k1":1, "k2":["a", "b"]}', '$.k2', 2); +SELECT JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[1]', 'x'); +SELECT JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[2]', 'x'); +SELECT JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[3]', 'x'); +SELECT JSON_ARRAY_INSERT('["a", {"b": [1, 2]}, [3, 4]]', '$[4]', 'x'); + select json_contains('{"k1":123, "k2":345}', '123', '$.k1'); select json_contains('"you"', '"you"'); select json_contains('"youth"', '"you"'); @@ -37,6 +42,24 @@ select json_extract('{"key1":"asd", "key2":[2,3]}', "$.key1", "$.key2"); select json_extract('{"key1":5, "key2":[2,3]}', "$.key1", "$.key2"); select json_extract('{"key0":true, "key1":"qwe"}', "$.key1"); +select json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.b.k1', 'word'); +select json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.d[3]', 3); +select json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.a[2]', 2); +select json_insert('{"a":1, "b":{"c":1}, "d":[1, 2]}', '$.b.c', 'word'); + +select json_set('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.c', '[true, false]'); + +select json_replace('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.c', '[true, false]'); +select json_replace('{ "a": 1, "b": [2, 3]}', '$.a', 10, '$.b', '[true, false]'); + +set @j = '["a", ["b", "c"], "d"]'; +select json_remove(@j, '$[0]'); +select json_remove(@j, '$[1]'); +select json_remove(@j, '$[2]'); +set @j = '{"a": 1, "b": [2, 3]}'; +select json_remove(@j, '$.b'); +select json_remove(@j, '$.a'); + select json_object("ki", 1, "mi", "ya"); select json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2"); @@ -52,3 +75,23 @@ select json_type('[123, "k2", 345]'); select json_type("true"); select json_type('123'); +select json_keys('{"a":{"c":1, "d":2}, "b":2}'); +select json_keys('{"a":{"c":1, "d":2}, "b":2}', "$.a"); +select json_keys('{"a":{"c":1, "d":2}, "b":2}', "$.b"); + +SET @j = '["abc", [{"k": "10"}, "def"], {"x":"abc"}, {"y":"bcd"}]'; +select json_search(@j, 'one', 'abc'); +select json_search(@j, 'all', 'abc'); +select json_search(@j, 'all', 'abc', NULL, '$[2]'); +select json_search(@j, 'all', 'abc', NULL, '$'); +select json_search(@j, 'all', '10', NULL, '$'); +select json_search(@j, 'all', '10', NULL, '$[*]'); +select json_search(@j, 'all', '10', NULL, '$[*][0].k'); + + +select json_unquote('"abc"'); + +select json_object("a", json_object("b", "abcd")); +select json_object("a", '{"b": "abcd"}'); +select json_object("a", cast('{"b": "abcd"}' as json)); + diff --git a/sql/item.h b/sql/item.h index d0d845711f7..20f3be17cce 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1457,6 +1457,7 @@ public: virtual void set_result_field(Field *field) {} virtual bool is_result_field() { return 0; } virtual bool is_bool_type() { return false; } + virtual bool is_json_type() { return false; } /* This is to handle printing of default values */ virtual bool need_parentheses_in_default() { return false; } virtual void save_in_result_field(bool no_conversions) {} @@ -5763,4 +5764,15 @@ public: void close() {} }; + +/* + It's used in ::fix_fields() methods of LIKE and JSON_SEARCH + functions to handle the ESCAPE parameter. + This parameter is quite non-standard so the specific function. +*/ +bool fix_escape_item(THD *thd, Item *escape_item, String *tmp_str, + bool escape_used_in_parsing, CHARSET_INFO *cmp_cs, + int *escape); + + #endif /* SQL_ITEM_INCLUDED */ diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 193af8631ba..e10a4ba573f 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -5124,13 +5124,10 @@ bool Item_func_like::with_sargable_pattern() const } -bool Item_func_like::fix_fields(THD *thd, Item **ref) +bool fix_escape_item(THD *thd, Item *escape_item, String *tmp_str, + bool escape_used_in_parsing, CHARSET_INFO *cmp_cs, + int *escape) { - DBUG_ASSERT(fixed == 0); - if (Item_bool_func2::fix_fields(thd, ref) || - escape_item->fix_fields(thd, &escape_item)) - return TRUE; - if (!escape_item->const_during_execution()) { my_error(ER_WRONG_ARGUMENTS,MYF(0),"ESCAPE"); @@ -5140,7 +5137,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) if (escape_item->const_item()) { /* If we are on execution stage */ - String *escape_str= escape_item->val_str(&cmp_value1); + String *escape_str= escape_item->val_str(tmp_str); if (escape_str) { const char *escape_str_ptr= escape_str->ptr(); @@ -5153,7 +5150,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) return TRUE; } - if (use_mb(cmp_collation.collation)) + if (use_mb(cmp_cs)) { CHARSET_INFO *cs= escape_str->charset(); my_wc_t wc; @@ -5161,7 +5158,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) (const uchar*) escape_str_ptr, (const uchar*) escape_str_ptr + escape_str->length()); - escape= (int) (rc > 0 ? wc : '\\'); + *escape= (int) (rc > 0 ? wc : '\\'); } else { @@ -5170,25 +5167,40 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) code instead of Unicode code as "escape" argument. Convert to "cs" if charset of escape differs. */ - CHARSET_INFO *cs= cmp_collation.collation; uint32 unused; if (escape_str->needs_conversion(escape_str->length(), - escape_str->charset(), cs, &unused)) + escape_str->charset(),cmp_cs,&unused)) { char ch; uint errors; - uint32 cnvlen= copy_and_convert(&ch, 1, cs, escape_str_ptr, + uint32 cnvlen= copy_and_convert(&ch, 1, cmp_cs, escape_str_ptr, escape_str->length(), escape_str->charset(), &errors); - escape= cnvlen ? ch : '\\'; + *escape= cnvlen ? ch : '\\'; } else - escape= escape_str_ptr ? *escape_str_ptr : '\\'; + *escape= escape_str_ptr ? *escape_str_ptr : '\\'; } } else - escape= '\\'; + *escape= '\\'; + } + + return FALSE; +} + +bool Item_func_like::fix_fields(THD *thd, Item **ref) +{ + DBUG_ASSERT(fixed == 0); + if (Item_bool_func2::fix_fields(thd, ref) || + escape_item->fix_fields(thd, &escape_item) || + fix_escape_item(thd, escape_item, &cmp_value1, escape_used_in_parsing, + cmp_collation.collation, &escape)) + return TRUE; + + if (escape_item->const_item()) + { /* We could also do boyer-more for non-const items, but as we would have to recompute the tables for each row it's not worth it. diff --git a/sql/item_create.cc b/sql/item_create.cc index cf6f24eddb7..9a1a5c62b5b 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -1786,6 +1786,19 @@ protected: }; +class Create_func_json_keys: public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list); + + static Create_func_json_keys s_singleton; + +protected: + Create_func_json_keys() {} + virtual ~Create_func_json_keys() {} +}; + + class Create_func_json_contains: public Create_native_func { public: @@ -1825,6 +1838,19 @@ protected: }; +class Create_func_json_search : public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list); + + static Create_func_json_search s_singleton; + +protected: + Create_func_json_search() {} + virtual ~Create_func_json_search() {} +}; + + class Create_func_json_array : public Create_native_func { public: @@ -1851,6 +1877,71 @@ protected: }; +class Create_func_json_array_insert : public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list); + + static Create_func_json_array_insert s_singleton; + +protected: + Create_func_json_array_insert() {} + virtual ~Create_func_json_array_insert() {} +}; + + +class Create_func_json_insert : public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list); + + static Create_func_json_insert s_singleton; + +protected: + Create_func_json_insert() {} + virtual ~Create_func_json_insert() {} +}; + + +class Create_func_json_set : public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list); + + static Create_func_json_set s_singleton; + +protected: + Create_func_json_set() {} + virtual ~Create_func_json_set() {} +}; + + +class Create_func_json_replace : public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list); + + static Create_func_json_replace s_singleton; + +protected: + Create_func_json_replace() {} + virtual ~Create_func_json_replace() {} +}; + + +class Create_func_json_remove : public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list); + + static Create_func_json_remove s_singleton; + +protected: + Create_func_json_remove() {} + virtual ~Create_func_json_remove() {} +}; + + class Create_func_json_object : public Create_native_func { public: @@ -1903,6 +1994,19 @@ protected: }; +class Create_func_json_unquote : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_json_unquote s_singleton; + +protected: + Create_func_json_unquote() {} + virtual ~Create_func_json_unquote() {} +}; + + class Create_func_last_day : public Create_func_arg1 { public: @@ -4830,6 +4934,15 @@ Create_func_json_quote::create_1_arg(THD *thd, Item *arg1) } +Create_func_json_unquote Create_func_json_unquote::s_singleton; + +Item* +Create_func_json_unquote::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_json_unquote(thd, arg1); +} + + Create_func_last_day Create_func_last_day::s_singleton; Item* @@ -4885,6 +4998,134 @@ Create_func_json_array_append::create_native(THD *thd, LEX_STRING name, } +Create_func_json_array_insert Create_func_json_array_insert::s_singleton; + +Item* +Create_func_json_array_insert::create_native(THD *thd, LEX_STRING name, + List<Item> *item_list) +{ + Item *func= NULL; + int arg_count= 0; + + if (item_list != NULL) + arg_count= item_list->elements; + + if (arg_count < 3 || (arg_count & 1) == 0 /*is even*/) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + } + else + { + func= new (thd->mem_root) Item_func_json_array_insert(thd, *item_list); + } + + return func; +} + + +Create_func_json_insert Create_func_json_insert::s_singleton; + +Item* +Create_func_json_insert::create_native(THD *thd, LEX_STRING name, + List<Item> *item_list) +{ + Item *func= NULL; + int arg_count= 0; + + if (item_list != NULL) + arg_count= item_list->elements; + + if (arg_count < 3 || (arg_count & 1) == 0 /*is even*/) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + } + else + { + func= new (thd->mem_root) Item_func_json_insert(true, false, + thd, *item_list); + } + + return func; +} + + +Create_func_json_set Create_func_json_set::s_singleton; + +Item* +Create_func_json_set::create_native(THD *thd, LEX_STRING name, + List<Item> *item_list) +{ + Item *func= NULL; + int arg_count= 0; + + if (item_list != NULL) + arg_count= item_list->elements; + + if (arg_count < 3 || (arg_count & 1) == 0 /*is even*/) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + } + else + { + func= new (thd->mem_root) Item_func_json_insert(true, true, + thd, *item_list); + } + + return func; +} + + +Create_func_json_replace Create_func_json_replace::s_singleton; + +Item* +Create_func_json_replace::create_native(THD *thd, LEX_STRING name, + List<Item> *item_list) +{ + Item *func= NULL; + int arg_count= 0; + + if (item_list != NULL) + arg_count= item_list->elements; + + if (arg_count < 3 || (arg_count & 1) == 0 /*is even*/) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + } + else + { + func= new (thd->mem_root) Item_func_json_insert(false, true, + thd, *item_list); + } + + return func; +} + + +Create_func_json_remove Create_func_json_remove::s_singleton; + +Item* +Create_func_json_remove::create_native(THD *thd, LEX_STRING name, + List<Item> *item_list) +{ + Item *func= NULL; + int arg_count= 0; + + if (item_list != NULL) + arg_count= item_list->elements; + + if (arg_count < 2 /*json_doc, path [,path]*/) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + } + else + { + func= new (thd->mem_root) Item_func_json_remove(thd, *item_list); + } + + return func; +} + + Create_func_json_object Create_func_json_object::s_singleton; Item* @@ -4990,6 +5231,31 @@ Create_func_json_contains::create_native(THD *thd, LEX_STRING name, } +Create_func_json_keys Create_func_json_keys::s_singleton; + +Item* +Create_func_json_keys::create_native(THD *thd, LEX_STRING name, + List<Item> *item_list) +{ + Item *func= NULL; + int arg_count= 0; + + if (item_list != NULL) + arg_count= item_list->elements; + + if (arg_count < 1 || arg_count > 2 /* json_doc, [path]...*/) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + } + else + { + func= new (thd->mem_root) Item_func_json_keys(thd, *item_list); + } + + return func; +} + + Create_func_json_contains_path Create_func_json_contains_path::s_singleton; Item* @@ -5040,6 +5306,31 @@ Create_func_json_extract::create_native(THD *thd, LEX_STRING name, } +Create_func_json_search Create_func_json_search::s_singleton; + +Item* +Create_func_json_search::create_native(THD *thd, LEX_STRING name, + List<Item> *item_list) +{ + Item *func= NULL; + int arg_count= 0; + + if (item_list != NULL) + arg_count= item_list->elements; + + if (arg_count < 3 /* json_doc, one_or_all, search_str, [escape_char[, path]...*/) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + } + else + { + func= new (thd->mem_root) Item_func_json_search(thd, *item_list); + } + + return func; +} + + Create_func_last_insert_id Create_func_last_insert_id::s_singleton; Item* @@ -6313,17 +6604,25 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("IS_USED_LOCK") }, BUILDER(Create_func_is_used_lock)}, { { C_STRING_WITH_LEN("JSON_ARRAY") }, BUILDER(Create_func_json_array)}, { { C_STRING_WITH_LEN("JSON_ARRAY_APPEND") }, BUILDER(Create_func_json_array_append)}, + { { C_STRING_WITH_LEN("JSON_ARRAY_INSERT") }, BUILDER(Create_func_json_array_insert)}, { { C_STRING_WITH_LEN("JSON_CONTAINS") }, BUILDER(Create_func_json_contains)}, { { C_STRING_WITH_LEN("JSON_CONTAINS_PATH") }, BUILDER(Create_func_json_contains_path)}, { { C_STRING_WITH_LEN("JSON_DEPTH") }, BUILDER(Create_func_json_depth)}, { { C_STRING_WITH_LEN("JSON_EXISTS") }, BUILDER(Create_func_json_exists)}, { { C_STRING_WITH_LEN("JSON_EXTRACT") }, BUILDER(Create_func_json_extract)}, + { { C_STRING_WITH_LEN("JSON_INSERT") }, BUILDER(Create_func_json_insert)}, + { { C_STRING_WITH_LEN("JSON_KEYS") }, BUILDER(Create_func_json_keys)}, { { C_STRING_WITH_LEN("JSON_LENGTH") }, BUILDER(Create_func_json_length)}, { { C_STRING_WITH_LEN("JSON_MERGE") }, BUILDER(Create_func_json_merge)}, { { C_STRING_WITH_LEN("JSON_QUERY") }, BUILDER(Create_func_json_query)}, { { C_STRING_WITH_LEN("JSON_QUOTE") }, BUILDER(Create_func_json_quote)}, { { C_STRING_WITH_LEN("JSON_OBJECT") }, BUILDER(Create_func_json_object)}, + { { C_STRING_WITH_LEN("JSON_REMOVE") }, BUILDER(Create_func_json_remove)}, + { { C_STRING_WITH_LEN("JSON_REPLACE") }, BUILDER(Create_func_json_replace)}, + { { C_STRING_WITH_LEN("JSON_SET") }, BUILDER(Create_func_json_set)}, + { { C_STRING_WITH_LEN("JSON_SEARCH") }, BUILDER(Create_func_json_search)}, { { C_STRING_WITH_LEN("JSON_TYPE") }, BUILDER(Create_func_json_type)}, + { { C_STRING_WITH_LEN("JSON_UNQUOTE") }, BUILDER(Create_func_json_unquote)}, { { C_STRING_WITH_LEN("JSON_VALID") }, BUILDER(Create_func_json_valid)}, { { C_STRING_WITH_LEN("JSON_VALUE") }, BUILDER(Create_func_json_value)}, { { C_STRING_WITH_LEN("LAST_DAY") }, BUILDER(Create_func_last_day)}, @@ -6716,6 +7015,9 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, res= new (thd->mem_root) Item_char_typecast(thd, a, len, real_cs); break; } + case ITEM_CAST_JSON: + res= new (thd->mem_root) Item_json_typecast(thd, a); + break; default: { DBUG_ASSERT(0); diff --git a/sql/item_func.h b/sql/item_func.h index ca7c4819012..374c16e3932 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -2197,7 +2197,7 @@ enum Cast_target { ITEM_CAST_BINARY, ITEM_CAST_SIGNED_INT, ITEM_CAST_UNSIGNED_INT, ITEM_CAST_DATE, ITEM_CAST_TIME, ITEM_CAST_DATETIME, ITEM_CAST_CHAR, - ITEM_CAST_DECIMAL, ITEM_CAST_DOUBLE + ITEM_CAST_DECIMAL, ITEM_CAST_DOUBLE, ITEM_CAST_JSON }; diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 80713710927..4fa2f02bedc 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -48,6 +48,45 @@ static bool eq_ascii_string(const CHARSET_INFO *cs, } +static bool append_simple(String *s, const char *a, uint a_len) +{ + if (!s->realloc_with_extra_if_needed(s->length() + a_len)) + { + s->q_append(a, a_len); + return FALSE; + } + + return TRUE; +} + + +static inline bool append_simple(String *s, const uchar *a, uint a_len) +{ + return append_simple(s, (const char *) a, a_len); +} + + +/* + Appends JSON string to the String object taking charsets in + consideration. +static int st_append_json(String *s, + CHARSET_INFO *json_cs, const uchar *js, uint js_len) +{ + int str_len= js_len * s->charset()->mbmaxlen; + + if (!s->reserve(str_len, 1024) && + (str_len= json_unescape(json_cs, js, js + js_len, + s->charset(), (uchar *) s->end(), (uchar *) s->end() + str_len)) > 0) + { + s->length(s->length() + str_len); + return 0; + } + + return js_len; +} +*/ + + /* Appends arbitrary String to the JSON string taking charsets in consideration. @@ -278,6 +317,50 @@ String *Item_func_json_quote::val_str(String *str) } +void Item_func_json_unquote::fix_length_and_dec() +{ + collation.set(&my_charset_utf8_general_ci); + max_length= args[0]->max_length; +} + + +String *Item_func_json_unquote::val_str(String *str) +{ + String *js= args[0]->val_str(&tmp_s); + json_engine_t je; + int c_len; + + if ((null_value= args[0]->null_value)) + return NULL; + + json_scan_start(&je, js->charset(),(const uchar *) js->ptr(), + (const uchar *) js->ptr() + js->length()); + + if (json_read_value(&je)) + goto error; + + if (je.value_type != JSON_VALUE_STRING) + return js; + + str->length(0); + str->set_charset(&my_charset_utf8_general_ci); + + if (str->realloc_with_extra_if_needed(je.value_len) || + (c_len= json_unescape(js->charset(), + je.value, je.value + je.value_len, + &my_charset_utf8_general_ci, + (uchar *) str->ptr(), (uchar *) (str->ptr() + je.value_len))) < 0) + goto error; + + str->length(c_len); + return str; + +error: + null_value= 1; + return 0; +} + + static int alloc_tmp_paths(THD *thd, uint n_paths, json_path_with_flags **paths,String **tmp_paths) { @@ -642,6 +725,27 @@ void Item_func_json_contains_path::cleanup() } +static int parse_one_or_all(Item *ooa_arg, + bool *ooa_parsed, bool ooa_constant, bool *mode_one) +{ + if (!*ooa_parsed) + { + char buff[20]; + String *res, tmp(buff, sizeof(buff), &my_charset_bin); + res= ooa_arg->val_str(&tmp); + *mode_one=eq_ascii_string(res->charset(), "one", + res->ptr(), res->length()); + if (!*mode_one) + { + if (!eq_ascii_string(res->charset(), "all", res->ptr(), res->length())) + return TRUE; + } + *ooa_parsed= ooa_constant; + } + return FALSE; +} + + longlong Item_func_json_contains_path::val_int() { String *js= args[0]->val_str(&tmp_js); @@ -652,20 +756,8 @@ longlong Item_func_json_contains_path::val_int() if ((null_value= args[0]->null_value)) return 0; - if (!ooa_parsed) - { - char buff[20]; - String *res, tmp(buff, sizeof(buff), &my_charset_bin); - res= args[1]->val_str(&tmp); - mode_one=eq_ascii_string(res->charset(), "one", - res->ptr(), res->length()); - if (!mode_one) - { - if (!eq_ascii_string(res->charset(), "all", res->ptr(), res->length())) - goto error; - } - ooa_parsed= ooa_constant; - } + if (parse_one_or_all(args[1], &ooa_parsed, ooa_constant, &mode_one)) + goto error; result= !mode_one; for (n_arg=2; n_arg < arg_count; n_arg++) @@ -742,6 +834,9 @@ static int append_json_value(String *str, Item *item, String *tmp_val) String *sv= item->val_str(tmp_val); if (item->null_value) goto append_null; + if (item->is_json_type()) + return str->append(sv->ptr(), sv->length()); + if (item->result_type() == STRING_RESULT) { return str->append("\"", 1) || @@ -915,6 +1010,117 @@ error: } +String *Item_func_json_array_insert::val_str(String *str) +{ + json_engine_t je; + String *js= args[0]->val_str(&tmp_js); + uint n_arg, n_path; + + DBUG_ASSERT(fixed == 1); + + if ((null_value= args[0]->null_value)) + return 0; + + for (n_arg=1, n_path=0; n_arg < arg_count; n_arg+=2, n_path++) + { + uint array_counters[JSON_DEPTH_LIMIT]; + json_path_with_flags *c_path= paths + n_path; + const char *item_pos; + uint n_item; + + if (!c_path->parsed) + { + String *s_p= args[n_arg]->val_str(tmp_paths+n_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()) || + c_path->p.last_step - 1 > c_path->p.steps || + c_path->p.last_step->type != JSON_PATH_ARRAY)) + goto error; + c_path->parsed= c_path->constant; + c_path->p.last_step--; + } + if (args[n_arg]->null_value) + goto null_return; + + 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)) + { + if (je.s.error) + goto error; + + /* Can't find the array to insert. */ + goto null_return; + } + + if (json_read_value(&je)) + goto error; + + if (je.value_type != JSON_VALUE_ARRAY) + { + /* Must be an array. */ + goto null_return; + } + + item_pos= 0; + n_item= 0; + + while (json_scan_next(&je) == 0 && + je.state != JST_ARRAY_END && item_pos == 0) + { + switch (je.state) + { + case JST_VALUE: + if (n_item == c_path->p.last_step[1].n_item) + { + item_pos= (const char *) je.s.c_str; + break; + } + n_item++; + break; + case JST_OBJ_START: + case JST_ARRAY_START: + if (json_skip_level(&je)) + break; + break; + default: + break; + } + } + + str->length(0); + str->set_charset(js->charset()); + if (!item_pos) + item_pos= (const char *) (je.s.c_str - je.sav_c_len); + + if (append_simple(str, js->ptr(), item_pos - js->ptr()) || + ((je.state == JST_ARRAY_END) ? + (n_item > 0 && str->append(", ", 2)) : str->append(" ", 1)) || + append_json_value(str, args[n_arg+1], &tmp_val) || + (je.state != JST_ARRAY_END && str->append(",", 1)) || + append_simple(str, item_pos, js->end() - item_pos)) + goto error; /* Out of memory. */ + + { + String *tmp_str= str; + str= &tmp_js; + js= tmp_str; + } + } + + return js; + +null_return: +error: + null_value= 1; + return 0; +} + + String *Item_func_json_object::val_str(String *str) { DBUG_ASSERT(fixed == 1); @@ -1107,3 +1313,754 @@ error: } +void Item_func_json_insert::fix_length_and_dec() +{ + uint n_arg; + ulonglong char_length; + + collation.set(args[0]->collation); + char_length= args[0]->max_char_length(); + + for (n_arg= 1; n_arg < arg_count; n_arg+= 2) + { + paths[n_arg-1].set_constant_flag(args[n_arg]->const_item()); + char_length+= args[n_arg+1]->max_char_length() + 4; + } + + fix_char_length_ulonglong(char_length); +} + + +String *Item_func_json_insert::val_str(String *str) +{ + json_engine_t je; + String *js= args[0]->val_str(&tmp_js); + uint n_arg, n_path; + json_string_t key_name; + + DBUG_ASSERT(fixed == 1); + + if ((null_value= args[0]->null_value)) + return 0; + + str->set_charset(js->charset()); + json_string_set_cs(&key_name, js->charset()); + + for (n_arg=1, n_path=0; n_arg < arg_count; n_arg+=2, n_path++) + { + uint array_counters[JSON_DEPTH_LIMIT]; + json_path_with_flags *c_path= paths + n_path; + const char *v_to; + const json_path_step_t *lp; + + if (!c_path->parsed) + { + String *s_p= args[n_arg]->val_str(tmp_paths+n_path); + if (s_p) + { + if (json_path_setup(&c_path->p,s_p->charset(), + (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length())) + goto error; + + /* We search to the last step. */ + c_path->p.last_step--; + } + c_path->parsed= c_path->constant; + } + if (args[n_arg]->null_value) + { + null_value= 1; + return 0; + } + + 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 (c_path->p.last_step >= c_path->p.steps && + json_find_path(&je, &c_path->p, &c_path->cur_step, array_counters)) + { + if (je.s.error) + goto error; + } + + if (json_read_value(&je)) + goto error; + + lp= c_path->p.last_step+1; + if (lp->type == JSON_PATH_ARRAY) + { + uint n_item= 0; + + if (je.value_type != JSON_VALUE_ARRAY) + { + const uchar *v_from= je.value_begin; + if (!mode_insert) + continue; + + str->length(0); + /* Wrap the value as an array. */ + if (append_simple(str, js->ptr(), (const char *) v_from - js->ptr()) || + str->append("[", 1)) + goto error; /* Out of memory. */ + + if (je.value_type == JSON_VALUE_OBJECT) + { + if (json_skip_level(&je)) + goto error; + } + + if (append_simple(str, v_from, je.s.c_str - v_from) || + str->append(", ", 2) || + append_json_value(str, args[n_arg+1], &tmp_val) || + str->append("]", 1) || + append_simple(str, je.s.c_str, js->end()-(const char *) je.s.c_str)) + goto error; /* Out of memory. */ + + goto continue_point; + } + + while (json_scan_next(&je) == 0 && je.state != JST_ARRAY_END) + { + switch (je.state) + { + case JST_VALUE: + if (n_item == lp->n_item) + goto v_found; + n_item++; + if (json_skip_array_item(&je)) + goto error; + break; + default: + break; + } + } + + if (je.s.error) + goto error; + + if (!mode_insert) + continue; + + v_to= (const char *) (je.s.c_str - je.sav_c_len); + str->length(0); + if (append_simple(str, js->ptr(), v_to - js->ptr()) || + str->append(", ", 2) || + append_json_value(str, args[n_arg+1], &tmp_val) || + append_simple(str, v_to, js->end() - v_to)) + goto error; /* Out of memory. */ + } + else /*JSON_PATH_KEY*/ + { + if (je.value_type != JSON_VALUE_OBJECT) + continue; + + while (json_scan_next(&je) == 0 && je.state != JST_OBJ_END) + { + switch (je.state) + { + case JST_KEY: + json_string_set_str(&key_name, lp->key, lp->key_end); + if (json_key_matches(&je, &key_name)) + goto v_found; + if (json_skip_key(&je)) + goto error; + break; + default: + break; + } + } + + if (je.s.error) + goto error; + + if (!mode_insert) + continue; + + v_to= (const char *) (je.s.c_str - je.sav_c_len); + str->length(0); + if (append_simple(str, js->ptr(), v_to - js->ptr()) || + str->append(", \"", 3) || + append_simple(str, lp->key, lp->key_end - lp->key) || + str->append("\":", 2) || + append_json_value(str, args[n_arg+1], &tmp_val) || + append_simple(str, v_to, js->end() - v_to)) + goto error; /* Out of memory. */ + } + + goto continue_point; + +v_found: + + if (!mode_replace) + continue; + + if (json_read_value(&je)) + goto error; + + v_to= (const char *) je.value_begin; + str->length(0); + if (!json_value_scalar(&je)) + { + if (json_skip_level(&je)) + goto error; + } + + if (append_simple(str, js->ptr(), v_to - js->ptr()) || + append_json_value(str, args[n_arg+1], &tmp_val) || + append_simple(str, je.s.c_str, js->end()-(const char *) je.s.c_str)) + goto error; /* Out of memory. */ +continue_point: + { + String *tmp= str; + str= &tmp_js; + js= tmp; + } + } + + return js; + +error: + null_value= 1; + return 0; +} + + +void Item_func_json_remove::fix_length_and_dec() +{ + collation.set(args[0]->collation); + max_length= args[0]->max_length; + + mark_constant_paths(paths, args+1, arg_count-1); +} + + +String *Item_func_json_remove::val_str(String *str) +{ + json_engine_t je; + String *js= args[0]->val_str(&tmp_js); + uint n_arg, n_path; + json_string_t key_name; + + DBUG_ASSERT(fixed == 1); + + if (args[0]->null_value) + goto null_return; + + str->set_charset(js->charset()); + json_string_set_cs(&key_name, js->charset()); + + for (n_arg=1, n_path=0; n_arg < arg_count; n_arg+=2, n_path++) + { + uint array_counters[JSON_DEPTH_LIMIT]; + json_path_with_flags *c_path= paths + n_path; + const char *rem_start, *rem_end; + const json_path_step_t *lp; + uint n_item= 0; + + if (!c_path->parsed) + { + String *s_p= args[n_arg]->val_str(tmp_paths+n_path); + if (s_p) + { + if (json_path_setup(&c_path->p,s_p->charset(), + (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length())) + goto error; + + /* We search to the last step. */ + c_path->p.last_step--; + if (c_path->p.last_step < c_path->p.steps) + goto null_return; + } + c_path->parsed= c_path->constant; + } + if (args[n_arg]->null_value) + { + null_value= 1; + return 0; + } + + 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)) + { + if (je.s.error) + goto error; + } + + if (json_read_value(&je)) + goto error; + + lp= c_path->p.last_step+1; + if (lp->type == JSON_PATH_ARRAY) + { + if (je.value_type != JSON_VALUE_ARRAY) + continue; + + while (json_scan_next(&je) == 0 && je.state != JST_ARRAY_END) + { + switch (je.state) + { + case JST_VALUE: + if (n_item == lp->n_item) + { + rem_start= (const char *) (je.s.c_str - + (n_item ? je.sav_c_len : 0)); + goto v_found; + } + n_item++; + if (json_skip_array_item(&je)) + goto error; + break; + default: + break; + } + } + + if (je.s.error) + goto error; + + continue; + } + else /*JSON_PATH_KEY*/ + { + if (je.value_type != JSON_VALUE_OBJECT) + continue; + + while (json_scan_next(&je) == 0 && je.state != JST_OBJ_END) + { + switch (je.state) + { + case JST_KEY: + if (n_item == 0) + rem_start= (const char *) (je.s.c_str - je.sav_c_len); + json_string_set_str(&key_name, lp->key, lp->key_end); + if (json_key_matches(&je, &key_name)) + { + goto v_found; + } + + if (json_skip_key(&je)) + goto error; + + rem_start= (const char *) je.s.c_str; + n_item++; + break; + default: + break; + } + } + + if (je.s.error) + goto error; + + continue; + } + +v_found: + + if (json_skip_key(&je) || json_scan_next(&je)) + goto error; + + rem_end= (je.state == JST_VALUE) ? + (const char *) je.s.c_str : (const char *) (je.s.c_str - je.sav_c_len); + + str->length(0); + + if (append_simple(str, js->ptr(), rem_start - js->ptr()) || + append_simple(str, rem_end, js->end() - rem_end)) + goto error; /* Out of memory. */ + + { + String *tmp= str; + str= &tmp_js; + js= tmp; + } + } + + return js; + +null_return: +error: + null_value= 1; + return 0; +} + + +void Item_func_json_keys::fix_length_and_dec() +{ + collation.set(args[0]->collation); + max_length= args[0]->max_length; + if (arg_count > 1) + path.set_constant_flag(args[1]->const_item()); +} + + +String *Item_func_json_keys::val_str(String *str) +{ + json_engine_t je; + String *js= args[0]->val_str(&tmp_js); + uint n_keys= 0; + uint array_counters[JSON_DEPTH_LIMIT]; + + if ((args[0]->null_value)) + goto null_return; + + json_scan_start(&je, js->charset(),(const uchar *) js->ptr(), + (const uchar *) js->ptr() + js->length()); + + if (arg_count < 2) + goto skip_search; + + if (!path.parsed) + { + String *s_p= args[1]->val_str(&tmp_path); + if (s_p && + json_path_setup(&path.p, s_p->charset(), (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length())) + goto err_return; + path.parsed= path.constant; + } + + if (args[1]->null_value) + goto null_return; + + path.cur_step= path.p.steps; + + if (json_find_path(&je, &path.p, &path.cur_step, array_counters)) + { + if (je.s.error) + goto err_return; + + goto null_return; + } + +skip_search: + if (json_read_value(&je)) + goto err_return; + + if (je.value_type != JSON_VALUE_OBJECT) + goto null_return; + + str->length(0); + if (str->append("[", 1)) + goto err_return; /* Out of memory. */ + /* Parse the OBJECT collecting the keys. */ + while (json_scan_next(&je) == 0 && je.state != JST_OBJ_END) + { + const uchar *key_start, *key_end; + + switch (je.state) + { + case JST_KEY: + key_start= je.s.c_str; + while (json_read_keyname_chr(&je) == 0) + { + key_end= je.s.c_str; + } + if (je.s.error || + (n_keys > 0 && str->append(", ", 2)) || + str->append("\"", 1) || + append_simple(str, key_start, key_end - key_start) || + str->append("\"", 1)) + goto err_return; + n_keys++; + break; + case JST_OBJ_START: + case JST_ARRAY_START: + if (json_skip_level(&je)) + break; + break; + default: + break; + } + } + + if (je.s.error || str->append("]", 1)) + goto err_return; + + null_value= 0; + return str; + +null_return: +err_return: + null_value= 1; + return 0; +} + + +bool Item_func_json_search::fix_fields(THD *thd, Item **ref) +{ + if (Item_json_str_multipath::fix_fields(thd, ref)) + return TRUE; + + if (arg_count < 4) + return FALSE; + + return fix_escape_item(thd, args[3], &tmp_js, true, + args[0]->collation.collation, &escape); +} + + +static const uint SQR_MAX_BLOB_WIDTH= sqrt(MAX_BLOB_WIDTH); + +void Item_func_json_search::fix_length_and_dec() +{ + collation.set(args[0]->collation); + + /* + It's rather difficult to estimate the length of the result. + I belive arglen^2 is the reasonable upper limit. + */ + if (args[0]->max_length > SQR_MAX_BLOB_WIDTH) + max_length= MAX_BLOB_WIDTH; + else + { + max_length= args[0]->max_length; + max_length*= max_length; + } + + ooa_constant= args[1]->const_item(); + ooa_parsed= FALSE; + + if (arg_count > 4) + mark_constant_paths(paths, args+4, arg_count-4); +} + + +int Item_func_json_search::compare_json_value_wild(json_engine_t *je, + const String *cmp_str) +{ + return my_wildcmp(collation.collation, + (const char *) je->value, (const char *) (je->value + je->value_len), + cmp_str->ptr(), cmp_str->end(), escape, wild_one, wild_many) ? 0 : 1; +} + + +static int append_json_path(String *str, const json_path_t *p) +{ + const json_path_step_t *c; + + if (str->append("\"$", 2)) + return TRUE; + + for (c= p->steps+1; c <= p->last_step; c++) + { + if (c->type == JSON_PATH_KEY) + { + if (str->append(".", 1) || + append_simple(str, c->key, c->key_end-c->key)) + return TRUE; + } + else /*JSON_PATH_ARRAY*/ + { + + if (str->append("[", 1) || + str->append_ulonglong(c->n_item) || + str->append("]", 1)) + return TRUE; + } + } + + return str->append("\"", 1); +} + + +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) + return -1; + + if (sa->type == JSON_PATH_ARRAY) + { + if (!sa->wild && sa->n_item != sb->n_item) + return -1; + } + else /* JSON_PATH_KEY */ + { + if (!sa->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; +} + + +static bool path_ok(const json_path_with_flags *paths_list, int n_paths, + const json_path_t *p) +{ + for (; n_paths > 0; n_paths--, paths_list++) + { + if (json_path_compare(&paths_list->p, p) >= 0) + return TRUE; + } + return FALSE; +} + + +String *Item_func_json_search::val_str(String *str) +{ + String *js= args[0]->val_str(&tmp_js); + String *s_str= args[2]->val_str(&tmp_js); + json_engine_t je; + json_path_t p, sav_path; + uint n_arg; + + if (args[0]->null_value || args[2]->null_value) + goto null_return; + + if (parse_one_or_all(args[1], &ooa_parsed, ooa_constant, &mode_one)) + goto error; + + if (args[1]->null_value) + goto null_return; + + n_path_found= 0; + str->set_charset(js->charset()); + str->length(0); + + for (n_arg=4; n_arg < arg_count; n_arg++) + { + json_path_with_flags *c_path= paths + n_arg - 4; + if (!c_path->parsed) + { + String *s_p= args[n_arg]->val_str(tmp_paths + (n_arg-1)); + 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())) + goto error; + c_path->parsed= c_path->constant; + } + } + + json_scan_start(&je, js->charset(),(const uchar *) js->ptr(), + (const uchar *) js->ptr() + js->length()); + + p.last_step= p.steps; + p.steps[0].wild= 0; + p.steps[0].type= JSON_PATH_ARRAY; + p.steps[0].n_item= 0; + + do + { + switch (je.state) + { + case JST_KEY: + p.last_step->key= je.s.c_str; + while (json_read_keyname_chr(&je) == 0) + p.last_step->key_end= je.s.c_str; + if (je.s.error) + goto error; + /* Now we have je.state == JST_VALUE, so let's handle it. */ + + case JST_VALUE: + if (json_read_value(&je)) + goto error; + if (json_value_scalar(&je)) + { + if ((arg_count < 5 || path_ok(paths, n_arg - 4, &p)) && + compare_json_value_wild(&je, s_str) != 0) + { + ++n_path_found; + if (n_path_found == 1) + { + sav_path= p; + sav_path.last_step= sav_path.steps + (p.last_step - p.steps); + } + else + { + if (n_path_found == 2) + { + if (str->append("[", 1) || + append_json_path(str, &sav_path)) + goto error; + } + if (str->append(", ", 2) || append_json_path(str, &p)) + goto error; + } + + if (mode_one) + goto end; + } + if (p.last_step->type == JSON_PATH_ARRAY) + p.last_step->n_item++; + + } + else + { + p.last_step++; + if (je.value_type == JSON_VALUE_ARRAY) + { + p.last_step->type= JSON_PATH_ARRAY; + p.last_step->n_item= 0; + } + else /*JSON_VALUE_OBJECT*/ + p.last_step->type= JSON_PATH_KEY; + } + + break; + case JST_OBJ_END: + case JST_ARRAY_END: + p.last_step--; + if (p.last_step->type == JSON_PATH_ARRAY) + p.last_step->n_item++; + break; + default: + break; + } + } while (json_scan_next(&je) == 0); + + if (je.s.error) + goto error; + +end: + if (n_path_found == 0) + goto null_return; + if (n_path_found == 1) + { + if (append_json_path(str, &sav_path)) + goto error; + } + else + { + if (str->append("]", 1)) + goto error; + } + + return str; + + +null_return: +error: + /* TODO: launch error messages. */ + null_value= 1; + return 0; +} + + +void Item_json_typecast::fix_length_and_dec() +{ + maybe_null= args[0]->maybe_null; + max_length= args[0]->max_length; +} + + diff --git a/sql/item_jsonfunc.h b/sql/item_jsonfunc.h index 54da67b5ab9..d2d68b4c391 100644 --- a/sql/item_jsonfunc.h +++ b/sql/item_jsonfunc.h @@ -123,6 +123,21 @@ public: }; +class Item_func_json_unquote: public Item_str_func +{ +protected: + String tmp_s; + +public: + Item_func_json_unquote(THD *thd, Item *s): Item_str_func(thd, s) {} + const char *func_name() const { return "json_unquote"; } + void fix_length_and_dec(); + String *val_str(String *); + Item *get_copy(THD *thd, MEM_ROOT *mem_root) + { return get_item_copy<Item_func_json_unquote>(thd, mem_root, this); } +}; + + class Item_json_str_multipath: public Item_str_func { protected: @@ -134,6 +149,7 @@ public: bool fix_fields(THD *thd, Item **ref); void cleanup(); virtual uint get_n_paths() const = 0; + bool is_json_type() { return true; } }; @@ -207,6 +223,7 @@ public: Item_func_json_array(THD *thd, List<Item> &list): Item_str_func(thd, list) {} String *val_str(String *); + bool is_json_type() { return true; } void fix_length_and_dec(); const char *func_name() const { return "json_array"; } Item *get_copy(THD *thd, MEM_ROOT *mem_root) @@ -231,6 +248,18 @@ public: }; +class Item_func_json_array_insert: public Item_func_json_array_append +{ +public: + Item_func_json_array_insert(THD *thd, List<Item> &list): + Item_func_json_array_append(thd, list) {} + String *val_str(String *); + const char *func_name() const { return "json_array_insert"; } + Item *get_copy(THD *thd, MEM_ROOT *mem_root) + { return get_item_copy<Item_func_json_array_insert>(thd, mem_root, this); } +}; + + class Item_func_json_object: public Item_func_json_array { public: @@ -239,6 +268,7 @@ public: Item_func_json_object(THD *thd, List<Item> &list): Item_func_json_array(thd, list) {} String *val_str(String *); + bool is_json_type() { return true; } const char *func_name() const { return "json_object"; } Item *get_copy(THD *thd, MEM_ROOT *mem_root) { return get_item_copy<Item_func_json_object>(thd, mem_root, this); } @@ -253,6 +283,7 @@ public: Item_func_json_merge(THD *thd, List<Item> &list): Item_func_json_array(thd, list) {} String *val_str(String *); + bool is_json_type() { return true; } const char *func_name() const { return "json_merge"; } Item *get_copy(THD *thd, MEM_ROOT *mem_root) { return get_item_copy<Item_func_json_merge>(thd, mem_root, this); } @@ -301,4 +332,102 @@ public: }; +class Item_func_json_insert: public Item_json_str_multipath +{ +protected: + String tmp_js; + String tmp_val; + bool mode_insert, mode_replace; +public: + Item_func_json_insert(bool i_mode, bool r_mode, THD *thd, List<Item> &list): + Item_json_str_multipath(thd, list), + mode_insert(i_mode), mode_replace(r_mode) {} + void fix_length_and_dec(); + String *val_str(String *); + uint get_n_paths() const { return arg_count/2; } + const char *func_name() const + { + return mode_insert ? + (mode_replace ? "json_set" : "json_insert") : "json_update"; + } + Item *get_copy(THD *thd, MEM_ROOT *mem_root) + { return get_item_copy<Item_func_json_insert>(thd, mem_root, this); } +}; + + +class Item_func_json_remove: public Item_json_str_multipath +{ +protected: + String tmp_js; +public: + Item_func_json_remove(THD *thd, List<Item> &list): + Item_json_str_multipath(thd, list) {} + void fix_length_and_dec(); + String *val_str(String *); + uint get_n_paths() const { return arg_count - 1; } + const char *func_name() const { return "json_remove"; } + Item *get_copy(THD *thd, MEM_ROOT *mem_root) + { return get_item_copy<Item_func_json_remove>(thd, mem_root, this); } +}; + + +class Item_func_json_keys: public Item_str_func +{ +protected: + json_path_with_flags path; + String tmp_js, tmp_path; + +public: + Item_func_json_keys(THD *thd, List<Item> &list): + Item_str_func(thd, list) {} + const char *func_name() const { return "json_keys"; } + void fix_length_and_dec(); + String *val_str(String *); + Item *get_copy(THD *thd, MEM_ROOT *mem_root) + { return get_item_copy<Item_func_json_keys>(thd, mem_root, this); } +}; + + +class Item_func_json_search: public Item_json_str_multipath +{ +protected: + String tmp_js; + bool mode_one; + bool ooa_constant, ooa_parsed; + int escape; + int n_path_found; + json_path_t sav_path; + + int compare_json_value_wild(json_engine_t *je, const String *cmp_str); + +public: + Item_func_json_search(THD *thd, List<Item> &list): + Item_json_str_multipath(thd, list) {} + const char *func_name() const { return "json_search"; } + bool fix_fields(THD *thd, Item **ref); + void fix_length_and_dec(); + String *val_str(String *); + uint get_n_paths() const { return arg_count > 4 ? arg_count - 4 : 0; } + Item *get_copy(THD *thd, MEM_ROOT *mem_root) + { return get_item_copy<Item_func_json_search>(thd, mem_root, this); } +}; + + +class Item_json_typecast: public Item_str_func +{ +public: + Item_json_typecast(THD *thd, Item *a): Item_str_func(thd, a) {} + const char *func_name() const { return "cast_as_json"; } + bool is_json_type() { return true; } + void fix_length_and_dec(); + String *val_str(String *str) + { + return args[0]->val_str(str); + } + + Item *get_copy(THD *thd, MEM_ROOT *mem_root) + { return get_item_copy<Item_json_typecast>(thd, mem_root, this); } +}; + + #endif /* ITEM_JSONFUNC_INCLUDED */ diff --git a/sql/lex.h b/sql/lex.h index 527ddb81c4b..114bafeee18 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -309,6 +309,7 @@ static SYMBOL symbols[] = { { "ITERATE", SYM(ITERATE_SYM)}, { "INVOKER", SYM(INVOKER_SYM)}, { "JOIN", SYM(JOIN_SYM)}, + { "JSON", SYM(JSON_SYM)}, { "KEY", SYM(KEY_SYM)}, { "KEYS", SYM(KEYS)}, { "KEY_BLOCK_SIZE", SYM(KEY_BLOCK_SIZE)}, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index e17a514a391..f2aea2bee6e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1324,6 +1324,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token ISSUER_SYM %token ITERATE_SYM %token JOIN_SYM /* SQL-2003-R */ +%token JSON_SYM %token KEYS %token KEY_BLOCK_SIZE %token KEY_SYM /* SQL-2003-N */ @@ -10709,6 +10710,7 @@ cast_type: } | cast_type_numeric { $$= $1; Lex->charset= NULL; } | cast_type_temporal { $$= $1; Lex->charset= NULL; } + | JSON_SYM { $$.set(ITEM_CAST_JSON); } ; cast_type_numeric: @@ -13226,11 +13228,10 @@ opt_extended_describe: opt_format_json: /* empty */ {} + | FORMAT_SYM '=' JSON_SYM { Lex->explain_json= true; } | FORMAT_SYM '=' ident_or_text { - if (!my_strcasecmp(system_charset_info, $3.str, "JSON")) - Lex->explain_json= true; - else if (!my_strcasecmp(system_charset_info, $3.str, "TRADITIONAL")) + if (!my_strcasecmp(system_charset_info, $3.str, "TRADITIONAL")) DBUG_ASSERT(Lex->explain_json==false); else my_yyabort_error((ER_UNKNOWN_EXPLAIN_FORMAT, MYF(0), $3.str)); diff --git a/storage/connect/mysql-test/connect/disabled.def b/storage/connect/mysql-test/connect/disabled.def index a97ba67d73e..64d7ece3fe1 100644 --- a/storage/connect/mysql-test/connect/disabled.def +++ b/storage/connect/mysql-test/connect/disabled.def @@ -13,5 +13,6 @@ jdbc : Variable settings depend on machine configuration jdbc_new : Variable settings depend on machine configuration jdbc_oracle : Variable settings depend on machine configuration jdbc_postgresql : Variable settings depend on machine configuration +json : TABLE_TYPE = JSON conflicts with the SQL syntax json_udf : conflicts with the server JSON functions json_udf_bin : conflicts with the server JSON functions diff --git a/strings/json_lib.c b/strings/json_lib.c index 3f55280e3fa..662207c3899 100644 --- a/strings/json_lib.c +++ b/strings/json_lib.c @@ -1174,7 +1174,7 @@ static int handle_match(json_engine_t *je, json_path_t *p, Check if the name of the current JSON key matches the step of the path. */ -static int json_key_matches(json_engine_t *je, json_string_t *k) +int json_key_matches(json_engine_t *je, json_string_t *k) { while (json_read_keyname_chr(je) == 0) { @@ -1409,6 +1409,8 @@ int json_unescape(CHARSET_INFO *json_cs, CHARSET_INFO *res_cs, uchar *res, uchar *res_end) { json_string_t s; + const uchar *res_b= res; + json_string_setup(&s, json_cs, json_str, json_end); while (json_read_string_const_chr(&s) == 0) { @@ -1434,7 +1436,7 @@ int json_unescape(CHARSET_INFO *json_cs, return -1; } - return s.error ? 1 : 0; + return s.error==JE_EOS ? res - res_b : -1; } |