Browse Source
MDEV-9143 JSON_xxx functions.
MDEV-9143 JSON_xxx functions.
strings/json_lib.c added as a JSON library. SQL frunction added with sql/item_jsonfunc.h/ccpull/241/merge
21 changed files with 4225 additions and 34 deletions
-
1CMakeLists.txt
-
1include/CMakeLists.txt
-
356include/json_lib.h
-
3libmysqld/CMakeLists.txt
-
123mysql-test/r/func_json.result
-
2mysql-test/r/parser_precedence.result
-
54mysql-test/t/func_json.test
-
2sql/CMakeLists.txt
-
15sql/item.h
-
474sql/item_create.cc
-
1109sql/item_jsonfunc.cc
-
304sql/item_jsonfunc.h
-
21sql/item_xmlfunc.cc
-
5sql/item_xmlfunc.h
-
4sql/sql_yacc.yy
-
2storage/connect/mysql-test/connect/disabled.def
-
2strings/CMakeLists.txt
-
19strings/ctype-ucs2.c
-
1553strings/json_lib.c
-
23unittest/json_lib/CMakeLists.txt
-
186unittest/json_lib/json_lib-t.c
@ -0,0 +1,356 @@ |
|||
#ifndef JSON_LIB_INCLUDED |
|||
#define JSON_LIB_INCLUDED |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
#define JSON_DEPTH_LIMIT 32 |
|||
|
|||
/* |
|||
When error happens, the c_next of the JSON engine contains the |
|||
character that caused the error, and the c_str is the position |
|||
in string where the error occurs. |
|||
*/ |
|||
enum json_errors { |
|||
JE_BAD_CHR= -1, /* Invalid character, charset handler cannot read it. */ |
|||
|
|||
JE_NOT_JSON_CHR= -2, /* Character met not used in JSON. */ |
|||
/* ASCII 00-08 for instance. */ |
|||
|
|||
JE_EOS= -3, /* Unexpected end of string. */ |
|||
|
|||
JE_SYN= -4, /* The next character breaks the JSON syntax. */ |
|||
|
|||
JE_STRING_CONST= -5, /* Character disallowed in string constant. */ |
|||
|
|||
JE_ESCAPING= -6, /* Error in the escaping. */ |
|||
|
|||
JE_DEPTH= -7, /* The limit on the JSON depth was overrun. */ |
|||
}; |
|||
|
|||
|
|||
typedef struct st_json_string_t |
|||
{ |
|||
const uchar *c_str; /* Current position in JSON string */ |
|||
const uchar *str_end; /* The end on the string. */ |
|||
my_wc_t c_next; /* UNICODE of the last read character */ |
|||
int error; /* error code. */ |
|||
|
|||
CHARSET_INFO *cs; /* Character set of the JSON string. */ |
|||
|
|||
my_charset_conv_mb_wc wc; /* UNICODE conversion function. */ |
|||
/* It's taken out of the cs just to speed calls. */ |
|||
} json_string_t; |
|||
|
|||
|
|||
void json_string_set_cs(json_string_t *s, CHARSET_INFO *i_cs); |
|||
void json_string_set_str(json_string_t *s, |
|||
const uchar *str, const uchar *end); |
|||
#define json_next_char(j) \ |
|||
(j)->wc((j)->cs, &(j)->c_next, (j)->c_str, (j)->str_end) |
|||
#define json_eos(j) ((j)->c_str >= (j)->str_end) |
|||
/* |
|||
read_string_const_chr() reads the next character of the string constant |
|||
and saves it to the js->c_next. |
|||
It takes into account possible escapings, so if for instance |
|||
the string is '\b', the read_string_const_chr() sets 8. |
|||
*/ |
|||
int json_read_string_const_chr(json_string_t *js); |
|||
|
|||
|
|||
/* |
|||
Various JSON-related operations expect JSON path as a parameter. |
|||
The path is a string like this "$.keyA[2].*" |
|||
The path itself is a number of steps specifying either a key or a position |
|||
in an array. Some of them can be wildcards. |
|||
So the representation of the JSON path is the json_path_t class |
|||
containing an array of json_path_step_t objects. |
|||
*/ |
|||
|
|||
|
|||
enum json_path_step_types |
|||
{ |
|||
JSON_PATH_KEY=0, |
|||
JSON_PATH_ARRAY=1 |
|||
}; |
|||
|
|||
|
|||
typedef struct st_json_path_step_t |
|||
{ |
|||
enum json_path_step_types type; /* The type of the step - KEY or ARRAY */ |
|||
int wild; /* If the step is a wildcard */ |
|||
const uchar *key; /* Pointer to the beginning of the key. */ |
|||
const uchar *key_end; /* Pointer to the end of the key. */ |
|||
uint n_item; /* Item number in an array. No meaning for the key step. */ |
|||
} json_path_step_t; |
|||
|
|||
|
|||
typedef struct st_json_path_t |
|||
{ |
|||
json_string_t s; /* The string to be parsed. */ |
|||
json_path_step_t steps[JSON_DEPTH_LIMIT]; /* Steps of the path. */ |
|||
json_path_step_t *last_step; /* Points to the last step. */ |
|||
|
|||
int mode_strict; /* TRUE if the path specified as 'strict' */ |
|||
} json_path_t; |
|||
|
|||
|
|||
int json_path_setup(json_path_t *p, |
|||
CHARSET_INFO *i_cs, const uchar *str, const uchar *end); |
|||
|
|||
|
|||
/* |
|||
The set of functions and structures below provides interface |
|||
to the JSON text parser. |
|||
Running the parser normally goes like this: |
|||
|
|||
json_engine_t j_eng; // structure keeps parser's data |
|||
json_scan_start(j_eng) // begin the parsing |
|||
|
|||
do |
|||
{ |
|||
// The parser has read next piece of JSON |
|||
// and set fields of j_eng structure accordingly. |
|||
// So let's see what we have: |
|||
switch (j_eng.state) |
|||
{ |
|||
case JST_KEY: |
|||
// Handle key name. See the json_read_keyname_chr() |
|||
// Probably compare it with the keyname we're looking for |
|||
case JST_VALUE: |
|||
// Handle value. It is either value of the key or an array item. |
|||
// see the json_read_value() |
|||
case JST_OBJ_START: |
|||
// parser found an object (the '{' in JSON) |
|||
case JST_OBJ_END: |
|||
// parser found the end of the object (the '}' in JSON) |
|||
case JST_ARRAY_START: |
|||
// parser found an array (the '[' in JSON) |
|||
case JST_ARRAY_END: |
|||
// parser found the end of the array (the ']' in JSON) |
|||
|
|||
}; |
|||
} while (json_scan_next() == 0); // parse next structure |
|||
|
|||
|
|||
if (j_eng.s.error) // we need to check why the loop ended. |
|||
// Did we get to the end of JSON, or came upon error. |
|||
{ |
|||
signal_error_in_JSON() |
|||
} |
|||
|
|||
|
|||
Parts of JSON can be quickly skipped. If we are not interested |
|||
in a particular key, we can just skip it with json_skip_key() call. |
|||
Similarly json_skip_level() goes right to the end of an object |
|||
or an array. |
|||
*/ |
|||
|
|||
|
|||
/* These are JSON parser states that user can expect and handle. */ |
|||
enum json_states { |
|||
JST_VALUE, /* value found */ |
|||
JST_KEY, /* key found */ |
|||
JST_OBJ_START, /* object */ |
|||
JST_OBJ_END, /* object ended */ |
|||
JST_ARRAY_START, /* array */ |
|||
JST_ARRAY_END, /* array ended */ |
|||
NR_JSON_USER_STATES |
|||
}; |
|||
|
|||
|
|||
enum json_value_types |
|||
{ |
|||
JSON_VALUE_OBJECT=0, |
|||
JSON_VALUE_ARRAY=1, |
|||
JSON_VALUE_STRING, |
|||
JSON_VALUE_NUMBER, |
|||
JSON_VALUE_TRUE, |
|||
JSON_VALUE_FALSE, |
|||
JSON_VALUE_NULL |
|||
}; |
|||
|
|||
|
|||
typedef struct st_json_engine_t |
|||
{ |
|||
json_string_t s; /* String to parse. */ |
|||
int sav_c_len; /* Length of the current character. |
|||
Can be more than 1 for multibyte charsets */ |
|||
|
|||
int state; /* The state of the parser. One of 'enum json_states'. |
|||
It tells us what construction of JSON we've just read. */ |
|||
|
|||
/* These values are only set after the json_read_value() call. */ |
|||
enum json_value_types value_type; /* type of the value.*/ |
|||
const uchar *value; /* Points to the value. */ |
|||
const uchar *value_begin;/* Points to where the value starts in the JSON. */ |
|||
|
|||
/* |
|||
In most cases the 'value' and 'value_begin' are equal. |
|||
They only differ if the value is a string constants. Then 'value_begin' |
|||
points to the starting quotation mark, while the 'value' - to |
|||
the first character of the string. |
|||
*/ |
|||
|
|||
const uchar *value_end; /* Points to the next character after the value. */ |
|||
int value_len; /* The length of the value. Does not count quotations for */ |
|||
/* string constants. */ |
|||
|
|||
int stack[JSON_DEPTH_LIMIT]; /* Keeps the stack of nested JSON structures. */ |
|||
int *stack_p; /* The 'stack' pointer. */ |
|||
} json_engine_t; |
|||
|
|||
|
|||
int json_scan_start(json_engine_t *je, |
|||
CHARSET_INFO *i_cs, const uchar *str, const uchar *end); |
|||
int json_scan_next(json_engine_t *j); |
|||
|
|||
|
|||
/* |
|||
json_read_keyname_chr() function assists parsing the name of an JSON key. |
|||
It only can be called when the json_engine is in JST_KEY. |
|||
The json_read_keyname_chr() reads one character of the name of the key, |
|||
and puts it in j_eng.s.next_c. |
|||
Typical usage is like this: |
|||
|
|||
if (j_eng.state == JST_KEY) |
|||
{ |
|||
while (json_read_keyname_chr(&j) == 0) |
|||
{ |
|||
//handle next character i.e. match it against the pattern |
|||
} |
|||
} |
|||
*/ |
|||
|
|||
int json_read_keyname_chr(json_engine_t *j); |
|||
|
|||
|
|||
/* |
|||
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 |
|||
the JST_VALUE state. |
|||
|
|||
Typical usage is like this: |
|||
|
|||
if (j_eng.state == JST_VALUE) |
|||
{ |
|||
json_read_value(&j_eng); |
|||
switch(j_eng.value_type) |
|||
{ |
|||
case JSON_VALUE_STRING: |
|||
// get the string |
|||
str= j_eng.value; |
|||
str_length= j_eng.value_len; |
|||
case JSON_VALUE_NUMBER: |
|||
// get the number |
|||
... etc |
|||
} |
|||
*/ |
|||
int json_read_value(json_engine_t *j); |
|||
|
|||
|
|||
/* |
|||
json_skip_key() makes parser skip the content of the current |
|||
JSON key quickly. |
|||
It can be called only when the json_engine state is JST_KEY. |
|||
Typical usage is: |
|||
|
|||
if (j_eng.state == JST_KEY) |
|||
{ |
|||
if (key_does_not_match(j_eng)) |
|||
json_skip_key(j_eng); |
|||
} |
|||
*/ |
|||
|
|||
int json_skip_key(json_engine_t *j); |
|||
|
|||
|
|||
/* |
|||
json_skip_level() makes parser quickly skip the JSON content |
|||
to the end of the current object or array. |
|||
It is used when we're not interested in the rest of an array |
|||
or the rest of the keys of an object. |
|||
*/ |
|||
int json_skip_level(json_engine_t *j); |
|||
|
|||
|
|||
#define json_skip_array_item json_skip_key |
|||
|
|||
/* |
|||
Checks if the current value is of scalar type - |
|||
not an OBJECT nor ARRAY. |
|||
*/ |
|||
#define json_value_scalar(je) ((je)->value_type > JSON_VALUE_ARRAY) |
|||
|
|||
/* |
|||
Look for the JSON PATH in the json string. |
|||
Function can be called several times with same JSON/PATH to |
|||
find multiple matches. |
|||
On the first call, the json_engine_t parameter should be |
|||
initialized with the JSON string, and the json_path_t with the JSON path |
|||
appropriately. The 'p_cur_step' should point at the first |
|||
step of the path. |
|||
The 'array_counters' is the array of JSON_DEPTH_LIMIT size. |
|||
It stores the array counters of the parsed JSON. |
|||
If function returns 0, it means it found the match. The position of |
|||
the match is je->s.c_str. Then we can call the json_find_path() |
|||
with same engine/path/p_cur_step to get the next match. |
|||
Non-zero return means no matches found. |
|||
Check je->s.error to see if there was an error in JSON. |
|||
*/ |
|||
int json_find_path(json_engine_t *je, |
|||
json_path_t *p, json_path_step_t **p_cur_step, |
|||
uint *array_counters); |
|||
|
|||
|
|||
typedef struct st_json_find_paths_t |
|||
{ |
|||
uint n_paths; |
|||
json_path_t *paths; |
|||
uint cur_depth; |
|||
uint *path_depths; |
|||
uint array_counters[JSON_DEPTH_LIMIT]; |
|||
} json_find_paths_t; |
|||
|
|||
|
|||
int json_find_paths_first(json_engine_t *je, json_find_paths_t *state, |
|||
uint n_paths, json_path_t *paths, uint *path_depths); |
|||
int json_find_paths_next(json_engine_t *je, json_find_paths_t *state); |
|||
|
|||
|
|||
/* |
|||
Converst JSON string constant into ordinary string constant |
|||
which can involve unpacking json escapes and changing character set. |
|||
Returns negative integer in the case of an error, |
|||
the length of the result otherwise. |
|||
*/ |
|||
int json_unescape(CHARSET_INFO *json_cs, |
|||
const uchar *json_str, const uchar *json_end, |
|||
CHARSET_INFO *res_cs, |
|||
uchar *res, uchar *res_end); |
|||
|
|||
/* |
|||
Converst ordinary string constant into JSON string constant. |
|||
which can involve appropriate escaping and changing character set. |
|||
Returns negative integer in the case of an error, |
|||
the length of the result otherwise. |
|||
*/ |
|||
int json_escape(CHARSET_INFO *str_cs, const uchar *str, const uchar *str_end, |
|||
CHARSET_INFO *json_cs, uchar *json, uchar *json_end); |
|||
|
|||
|
|||
/* |
|||
Appends the ASCII string to the json with the charset conversion. |
|||
*/ |
|||
int json_append_ascii(CHARSET_INFO *json_cs, |
|||
uchar *json, uchar *json_end, |
|||
const uchar *ascii, const uchar *ascii_end); |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
|
|||
#endif /* JSON_LIB_INCLUDED */ |
|||
|
@ -0,0 +1,123 @@ |
|||
select json_valid('[1, 2]'); |
|||
json_valid('[1, 2]') |
|||
1 |
|||
select json_valid('"string"}'); |
|||
json_valid('"string"}') |
|||
0 |
|||
select json_valid('{"key1":1, "key2":[2,3]}'); |
|||
json_valid('{"key1":1, "key2":[2,3]}') |
|||
1 |
|||
select json_valid('[false, true, null]'); |
|||
json_valid('[false, true, null]') |
|||
1 |
|||
select json_value('{"key1":123}', '$.key2'); |
|||
json_value('{"key1":123}', '$.key2') |
|||
NULL |
|||
select json_value('{"key1":123}', '$.key1'); |
|||
json_value('{"key1":123}', '$.key1') |
|||
123 |
|||
select json_value('{"key1":[1,2,3]}', '$.key1'); |
|||
json_value('{"key1":[1,2,3]}', '$.key1') |
|||
NULL |
|||
select json_value('{"key1": [1,2,3], "key1":123}', '$.key1'); |
|||
json_value('{"key1": [1,2,3], "key1":123}', '$.key1') |
|||
123 |
|||
select json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key2'); |
|||
json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key2') |
|||
NULL |
|||
select json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key1'); |
|||
json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key1') |
|||
{"a":1, "b":[1,2]} |
|||
select json_query('{"key1": 1}', '$.key1'); |
|||
json_query('{"key1": 1}', '$.key1') |
|||
NULL |
|||
select json_query('{"key1":123, "key1": [1,2,3]}', '$.key1'); |
|||
json_query('{"key1":123, "key1": [1,2,3]}', '$.key1') |
|||
[1,2,3] |
|||
select json_array(1); |
|||
json_array(1) |
|||
[1] |
|||
select json_array(1, "text", false, null); |
|||
json_array(1, "text", false, null) |
|||
[1, "text", false, null] |
|||
select json_array_append('["a", "b"]', '$', FALSE); |
|||
json_array_append('["a", "b"]', '$', FALSE) |
|||
["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_contains('{"k1":123, "k2":345}', '123', '$.k1'); |
|||
json_contains('{"k1":123, "k2":345}', '123', '$.k1') |
|||
1 |
|||
select json_contains('"you"', '"you"'); |
|||
json_contains('"you"', '"you"') |
|||
1 |
|||
select json_contains('"youth"', '"you"'); |
|||
json_contains('"youth"', '"you"') |
|||
0 |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]"); |
|||
json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]") |
|||
1 |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[10]"); |
|||
json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[10]") |
|||
0 |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.ma"); |
|||
json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.ma") |
|||
0 |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "one", "$.key1"); |
|||
json_contains_path('{"key1":1, "key2":[2,3]}', "one", "$.key1") |
|||
1 |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "one", "$.key1", "$.ma"); |
|||
json_contains_path('{"key1":1, "key2":[2,3]}', "one", "$.key1", "$.ma") |
|||
1 |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "aLl", "$.key1", "$.ma"); |
|||
json_contains_path('{"key1":1, "key2":[2,3]}', "aLl", "$.key1", "$.ma") |
|||
0 |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "aLl", "$.key1", "$.key2"); |
|||
json_contains_path('{"key1":1, "key2":[2,3]}', "aLl", "$.key1", "$.key2") |
|||
1 |
|||
select json_extract('{"key1":"asd", "key2":[2,3]}', "$.key1"); |
|||
json_extract('{"key1":"asd", "key2":[2,3]}', "$.key1") |
|||
asd |
|||
select json_extract('{"key1":"asd", "key2":[2,3]}', "$.keyX", "$.keyY"); |
|||
json_extract('{"key1":"asd", "key2":[2,3]}', "$.keyX", "$.keyY") |
|||
NULL |
|||
select json_extract('{"key1":"asd", "key2":[2,3]}', "$.key1", "$.key2"); |
|||
json_extract('{"key1":"asd", "key2":[2,3]}', "$.key1", "$.key2") |
|||
["asd", [2,3]] |
|||
select json_extract('{"key1":5, "key2":[2,3]}', "$.key1", "$.key2"); |
|||
json_extract('{"key1":5, "key2":[2,3]}', "$.key1", "$.key2") |
|||
[5, [2,3]] |
|||
select json_extract('{"key0":true, "key1":"qwe"}', "$.key1"); |
|||
json_extract('{"key0":true, "key1":"qwe"}', "$.key1") |
|||
qwe |
|||
select json_object("ki", 1, "mi", "ya"); |
|||
json_object("ki", 1, "mi", "ya") |
|||
{"ki": 1, "mi": "ya"} |
|||
select json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2"); |
|||
json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2") |
|||
1 |
|||
select json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[1]"); |
|||
json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[1]") |
|||
1 |
|||
select json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[10]"); |
|||
json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[10]") |
|||
0 |
|||
select json_quote('"string"'); |
|||
json_quote('"string"') |
|||
\"string\" |
|||
select json_merge('string', 123); |
|||
json_merge('string', 123) |
|||
["string", 123] |
|||
select json_type('{"k1":123, "k2":345}'); |
|||
json_type('{"k1":123, "k2":345}') |
|||
OBJECT |
|||
select json_type('[123, "k2", 345]'); |
|||
json_type('[123, "k2", 345]') |
|||
ARRAY |
|||
select json_type("true"); |
|||
json_type("true") |
|||
BOOLEAN |
|||
select json_type('123'); |
|||
json_type('123') |
|||
NUMBER |
@ -0,0 +1,54 @@ |
|||
select json_valid('[1, 2]'); |
|||
select json_valid('"string"}'); |
|||
select json_valid('{"key1":1, "key2":[2,3]}'); |
|||
select json_valid('[false, true, null]'); |
|||
|
|||
select json_value('{"key1":123}', '$.key2'); |
|||
select json_value('{"key1":123}', '$.key1'); |
|||
select json_value('{"key1":[1,2,3]}', '$.key1'); |
|||
select json_value('{"key1": [1,2,3], "key1":123}', '$.key1'); |
|||
|
|||
select json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key2'); |
|||
select json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key1'); |
|||
select json_query('{"key1": 1}', '$.key1'); |
|||
select json_query('{"key1":123, "key1": [1,2,3]}', '$.key1'); |
|||
|
|||
select json_array(1); |
|||
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_contains('{"k1":123, "k2":345}', '123', '$.k1'); |
|||
select json_contains('"you"', '"you"'); |
|||
select json_contains('"youth"', '"you"'); |
|||
|
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]"); |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[10]"); |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.ma"); |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "one", "$.key1"); |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "one", "$.key1", "$.ma"); |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "aLl", "$.key1", "$.ma"); |
|||
select json_contains_path('{"key1":1, "key2":[2,3]}', "aLl", "$.key1", "$.key2"); |
|||
|
|||
select json_extract('{"key1":"asd", "key2":[2,3]}', "$.key1"); |
|||
select json_extract('{"key1":"asd", "key2":[2,3]}', "$.keyX", "$.keyY"); |
|||
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_object("ki", 1, "mi", "ya"); |
|||
|
|||
select json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2"); |
|||
select json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[1]"); |
|||
select json_exists('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[10]"); |
|||
|
|||
select json_quote('"string"'); |
|||
|
|||
select json_merge('string', 123); |
|||
|
|||
select json_type('{"k1":123, "k2":345}'); |
|||
select json_type('[123, "k2", 345]'); |
|||
select json_type("true"); |
|||
select json_type('123'); |
|||
|
1109
sql/item_jsonfunc.cc
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,304 @@ |
|||
#ifndef ITEM_JSONFUNC_INCLUDED |
|||
#define ITEM_JSONFUNC_INCLUDED |
|||
|
|||
/* Copyright (c) 2016, MariaDB |
|||
|
|||
This program is free software; you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation; version 2 of the License. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program; if not, write to the Free Software |
|||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
|||
|
|||
|
|||
/* This file defines all JSON functions */ |
|||
|
|||
|
|||
#include <json_lib.h> |
|||
#include "item_cmpfunc.h" // Item_bool_func |
|||
#include "item_strfunc.h" // Item_str_func |
|||
|
|||
|
|||
class json_path_with_flags |
|||
{ |
|||
public: |
|||
json_path_t p; |
|||
bool constant; |
|||
bool parsed; |
|||
json_path_step_t *cur_step; |
|||
void set_constant_flag(bool s_constant) |
|||
{ |
|||
constant= s_constant; |
|||
parsed= FALSE; |
|||
} |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_valid: public Item_int_func |
|||
{ |
|||
protected: |
|||
String tmp_value; |
|||
|
|||
public: |
|||
Item_func_json_valid(THD *thd, Item *json) : Item_int_func(thd, json) {} |
|||
longlong val_int(); |
|||
const char *func_name() const { return "json_valid"; } |
|||
void fix_length_and_dec() |
|||
{ |
|||
Item_int_func::fix_length_and_dec(); |
|||
maybe_null= 1; |
|||
} |
|||
bool is_bool_type() { return true; } |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_valid>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_exists: public Item_int_func |
|||
{ |
|||
protected: |
|||
json_path_with_flags path; |
|||
String tmp_js, tmp_path; |
|||
|
|||
public: |
|||
Item_func_json_exists(THD *thd, Item *js, Item *path): |
|||
Item_int_func(thd, js, path) {} |
|||
const char *func_name() const { return "json_exists"; } |
|||
bool is_bool_type() { return true; } |
|||
void fix_length_and_dec(); |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_exists>(thd, mem_root, this); } |
|||
longlong val_int(); |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_value: public Item_str_func |
|||
{ |
|||
protected: |
|||
json_path_with_flags path; |
|||
String tmp_js, tmp_path; |
|||
|
|||
public: |
|||
Item_func_json_value(THD *thd, Item *js, Item *path): |
|||
Item_str_func(thd, js, path) {} |
|||
const char *func_name() const { return "json_value"; } |
|||
void fix_length_and_dec(); |
|||
String *val_str(String *); |
|||
virtual bool check_and_get_value(json_engine_t *je, String *res, int *error); |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_value>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_query: public Item_func_json_value |
|||
{ |
|||
public: |
|||
Item_func_json_query(THD *thd, Item *js, Item *path): |
|||
Item_func_json_value(thd, js, path) {} |
|||
const char *func_name() const { return "json_query"; } |
|||
bool check_and_get_value(json_engine_t *je, String *res, int *error); |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_query>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_quote: public Item_str_func |
|||
{ |
|||
protected: |
|||
String tmp_s; |
|||
|
|||
public: |
|||
Item_func_json_quote(THD *thd, Item *s): Item_str_func(thd, s) {} |
|||
const char *func_name() const { return "json_quote"; } |
|||
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_quote>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_json_str_multipath: public Item_str_func |
|||
{ |
|||
protected: |
|||
json_path_with_flags *paths; |
|||
String *tmp_paths; |
|||
public: |
|||
Item_json_str_multipath(THD *thd, List<Item> &list): |
|||
Item_str_func(thd, list), tmp_paths(0) {} |
|||
bool fix_fields(THD *thd, Item **ref); |
|||
void cleanup(); |
|||
virtual uint get_n_paths() const = 0; |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_extract: public Item_json_str_multipath |
|||
{ |
|||
protected: |
|||
String tmp_js; |
|||
public: |
|||
Item_func_json_extract(THD *thd, List<Item> &list): |
|||
Item_json_str_multipath(thd, list) {} |
|||
const char *func_name() const { return "json_extract"; } |
|||
void fix_length_and_dec(); |
|||
String *val_str(String *); |
|||
longlong val_int(); |
|||
uint get_n_paths() const { return arg_count - 1; } |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_extract>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_contains: public Item_int_func |
|||
{ |
|||
protected: |
|||
String tmp_js; |
|||
json_path_with_flags *paths; |
|||
String *tmp_paths; |
|||
bool a2_constant, a2_parsed; |
|||
String tmp_val, *val; |
|||
public: |
|||
Item_func_json_contains(THD *thd, List<Item> &list): |
|||
Item_int_func(thd, list), tmp_paths(0) {} |
|||
const char *func_name() const { return "json_contains"; } |
|||
bool fix_fields(THD *thd, Item **ref); |
|||
void fix_length_and_dec(); |
|||
void cleanup(); |
|||
longlong val_int(); |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_contains>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_contains_path: public Item_int_func |
|||
{ |
|||
protected: |
|||
String tmp_js; |
|||
json_path_with_flags *paths; |
|||
String *tmp_paths; |
|||
bool mode_one; |
|||
bool ooa_constant, ooa_parsed; |
|||
|
|||
public: |
|||
Item_func_json_contains_path(THD *thd, List<Item> &list): |
|||
Item_int_func(thd, list), tmp_paths(0) {} |
|||
const char *func_name() const { return "json_contains_path"; } |
|||
bool fix_fields(THD *thd, Item **ref); |
|||
void fix_length_and_dec(); |
|||
void cleanup(); |
|||
longlong val_int(); |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_contains_path>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_array: public Item_str_func |
|||
{ |
|||
protected: |
|||
String tmp_val; |
|||
public: |
|||
Item_func_json_array(THD *thd): |
|||
Item_str_func(thd) {} |
|||
Item_func_json_array(THD *thd, List<Item> &list): |
|||
Item_str_func(thd, list) {} |
|||
String *val_str(String *); |
|||
void fix_length_and_dec(); |
|||
const char *func_name() const { return "json_array"; } |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_array>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_array_append: public Item_json_str_multipath |
|||
{ |
|||
protected: |
|||
String tmp_js; |
|||
String tmp_val; |
|||
public: |
|||
Item_func_json_array_append(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/2; } |
|||
const char *func_name() const { return "json_array_append"; } |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_array_append>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_object: public Item_func_json_array |
|||
{ |
|||
public: |
|||
Item_func_json_object(THD *thd): |
|||
Item_func_json_array(thd) {} |
|||
Item_func_json_object(THD *thd, List<Item> &list): |
|||
Item_func_json_array(thd, list) {} |
|||
String *val_str(String *); |
|||
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); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_merge: public Item_func_json_array |
|||
{ |
|||
protected: |
|||
String tmp_val; |
|||
public: |
|||
Item_func_json_merge(THD *thd, List<Item> &list): |
|||
Item_func_json_array(thd, list) {} |
|||
String *val_str(String *); |
|||
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); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_length: public Item_int_func |
|||
{ |
|||
protected: |
|||
String tmp_js; |
|||
String tmp_path; |
|||
public: |
|||
Item_func_json_length(THD *thd, List<Item> &list): |
|||
Item_int_func(thd, list) {} |
|||
const char *func_name() const { return "json_length"; } |
|||
longlong val_int(); |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_length>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_depth: public Item_int_func |
|||
{ |
|||
protected: |
|||
String tmp_js; |
|||
public: |
|||
Item_func_json_depth(THD *thd, Item *js): Item_int_func(thd, js) {} |
|||
const char *func_name() const { return "json_depth"; } |
|||
longlong val_int(); |
|||
Item *get_copy(THD *thd, MEM_ROOT *mem_root) |
|||
{ return get_item_copy<Item_func_json_depth>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
class Item_func_json_type: public Item_str_func |
|||
{ |
|||
protected: |
|||
String tmp_js; |
|||
public: |
|||
Item_func_json_type(THD *thd, Item *js): Item_str_func(thd, js) {} |
|||
const char *func_name() const { return "json_type"; } |
|||
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_type>(thd, mem_root, this); } |
|||
}; |
|||
|
|||
|
|||
#endif /* ITEM_JSONFUNC_INCLUDED */ |
1553
strings/json_lib.c
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,23 @@ |
|||
# Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. |
|||
# |
|||
# This program is free software; you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation; version 2 of the License. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with this program; if not, write to the Free Software |
|||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|||
|
|||
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include |
|||
${CMAKE_SOURCE_DIR}/sql |
|||
${CMAKE_SOURCE_DIR}/regex |
|||
${CMAKE_SOURCE_DIR}/extra/yassl/include |
|||
${CMAKE_SOURCE_DIR}/unittest/mytap) |
|||
|
|||
# |
|||
MY_ADD_TESTS(json_lib LINK_LIBRARIES strings dbug) |
@ -0,0 +1,186 @@ |
|||
/* Copyright (c) 2016, MariaDB Corp. All rights reserved. |
|||
|
|||
This program is free software; you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation; version 2 of the License. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program; if not, write to the Free Software |
|||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
|||
|
|||
#include "my_config.h" |
|||
#include "config.h" |
|||
#include <tap.h> |
|||
#include <my_global.h> |
|||
#include <my_sys.h> |
|||
#include <json_lib.h> |
|||
|
|||
/* The character set used for JSON all over this test. */ |
|||
static CHARSET_INFO *ci; |
|||
|
|||
#define s_e(j) j, j + strlen((const char *) j) |
|||
|
|||
|
|||
struct st_parse_result |
|||
{ |
|||
int n_keys; |
|||
int n_values; |
|||
int n_arrays; |
|||
int n_objects; |
|||
int n_steps; |
|||
int error; |
|||
uchar keyname_csum; |
|||
}; |
|||
|
|||
|
|||
static void parse_json(const uchar *j, struct st_parse_result *result) |
|||
{ |
|||
json_engine_t je; |
|||
|
|||
bzero(result, sizeof(*result)); |
|||
|
|||
if (json_scan_start(&je, ci, s_e(j))) |
|||
return; |
|||
|
|||
do |
|||
{ |
|||
result->n_steps++; |
|||
switch (je.state) |
|||
{ |
|||
case JST_KEY: |
|||
result->n_keys++; |
|||
while (json_read_keyname_chr(&je) == 0) |
|||
{ |
|||
result->keyname_csum^= je.s.c_next; |
|||
} |
|||
if (je.s.error) |
|||
return; |
|||
break; |
|||
case JST_VALUE: |
|||
result->n_values++; |
|||
break; |
|||
case JST_OBJ_START: |
|||
result->n_objects++; |
|||
break; |
|||
case JST_ARRAY_START: |
|||
result->n_arrays++; |
|||
break; |
|||
default: |
|||
break; |
|||
}; |
|||
} while (json_scan_next(&je) == 0); |
|||
|
|||
result->error= je.s.error; |
|||
} |
|||
|
|||
|
|||
static const uchar *js0= (const uchar *) "123"; |
|||
static const uchar *js1= (const uchar *) "[123, \"text\"]"; |
|||
static const uchar *js2= (const uchar *) "{\"key1\":123, \"key2\":\"text\"}"; |
|||
static const uchar *js3= (const uchar *) "{\"key1\":{\"ikey1\":321}," |
|||
"\"key2\":[\"text\", 321]}"; |
|||
|
|||
/* |
|||
Test json_lib functions to parse JSON. |
|||
*/ |
|||
static void |
|||
test_json_parsing() |
|||
{ |
|||
struct st_parse_result r; |
|||
parse_json(js0, &r); |
|||
ok(r.n_steps == 1 && r.n_values == 1, "simple value"); |
|||
parse_json(js1, &r); |
|||
ok(r.n_steps == 5 && r.n_values == 3 && r.n_arrays == 1, "array"); |
|||
parse_json(js2, &r); |
|||
ok(r.n_steps == 5 && r.n_keys == 2 && r.n_objects == 1 && r.keyname_csum == 3, |
|||
"object"); |
|||
parse_json(js3, &r); |
|||
ok(r.n_steps == 12 && r.n_keys == 3 && r.n_objects == 2 && |
|||
r.n_arrays == 1 && r.keyname_csum == 44, |
|||
"complex json"); |
|||
} |
|||
|
|||
|
|||
static const uchar *p0= (const uchar *) "$.key1[12].*[*]"; |
|||
/* |
|||
Test json_lib functions to parse JSON path. |
|||
*/ |
|||
static void |
|||
test_path_parsing() |
|||
{ |
|||
json_path_t p; |
|||
if (json_path_setup(&p, ci, s_e(p0))) |
|||
return; |
|||
ok(p.last_step - p.steps == 4 && |
|||
p.steps[0].type == JSON_PATH_ARRAY && p.steps[0].wild == 1 && |
|||
p.steps[1].type == JSON_PATH_KEY && p.steps[1].wild == 0 && |
|||
p.steps[2].type == JSON_PATH_ARRAY && p.steps[2].n_item == 12 && |
|||
p.steps[3].type == JSON_PATH_KEY && p.steps[3].wild == 1 && |
|||
p.steps[4].type == JSON_PATH_ARRAY && p.steps[4].wild == 1, |
|||
"path"); |
|||
} |
|||
|
|||
|
|||
static const uchar *fj0=(const uchar *) "[{\"k0\":123, \"k1\":123, \"k1\":123}," |
|||
" {\"k3\":321, \"k4\":\"text\"}," |
|||
" {\"k1\":[\"text\"], \"k2\":123}]"; |
|||
static const uchar *fp0= (const uchar *) "$[*].k1"; |
|||
/* |
|||
Test json_lib functions to search through JSON. |
|||
*/ |
|||
static void |
|||
test_search() |
|||
{ |
|||
json_engine_t je; |
|||
json_path_t p; |
|||
json_path_step_t *cur_step; |
|||
int n_matches, scal_values; |
|||
uint array_counters[JSON_DEPTH_LIMIT]; |
|||
|
|||
if (json_scan_start(&je, ci, s_e(fj0)) || |
|||
json_path_setup(&p, ci, s_e(fp0))) |
|||
return; |
|||
|
|||
cur_step= p.steps; |
|||
n_matches= scal_values= 0; |
|||
while (json_find_path(&je, &p, &cur_step, array_counters) == 0) |
|||
{ |
|||
n_matches++; |
|||
if (json_read_value(&je)) |
|||
return; |
|||
if (json_value_scalar(&je)) |
|||
{ |
|||
scal_values++; |
|||
if (json_scan_next(&je)) |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
if (json_skip_level(&je) || json_scan_next(&je)) |
|||
return; |
|||
} |
|||
|
|||
} |
|||
|
|||
ok(n_matches == 3, "search"); |
|||
} |
|||
|
|||
|
|||
int main() |
|||
{ |
|||
ci= &my_charset_utf8_general_ci; |
|||
|
|||
plan(6); |
|||
diag("Testing json_lib functions."); |
|||
|
|||
test_json_parsing(); |
|||
test_path_parsing(); |
|||
test_search(); |
|||
|
|||
return exit_status(); |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue