diff --git a/include/json_lib.h b/include/json_lib.h index aabf97ad073..b2ea39cd728 100644 --- a/include/json_lib.h +++ b/include/json_lib.h @@ -69,17 +69,26 @@ int json_read_string_const_chr(json_string_t *js); */ +/* Path step types - actually bitmasks to let '&' or '|' operations. */ enum json_path_step_types { - JSON_PATH_KEY=0, - JSON_PATH_ARRAY=1 + JSON_PATH_KEY_NULL=0, + JSON_PATH_KEY=1, /* Must be equal to JSON_VALUE_OBJECT. */ + JSON_PATH_ARRAY=2, /* Must be equal to JSON_VALUE_ARRAY. */ + JSON_PATH_KEY_OR_ARRAY=3, + JSON_PATH_WILD=4, /* Step like .* or [*] */ + JSON_PATH_DOUBLE_WILD=8, /* Step like **.k or **[1] */ + JSON_PATH_KEY_WILD= 1+4, + JSON_PATH_KEY_DOUBLEWILD= 1+8, + JSON_PATH_ARRAY_WILD= 2+4, + JSON_PATH_ARRAY_DOUBLEWILD= 2+8 }; 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 */ + enum json_path_step_types type; /* The type of the step - */ + /* see json_path_step_types */ 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. */ @@ -162,8 +171,8 @@ enum json_states { enum json_value_types { - JSON_VALUE_OBJECT=0, - JSON_VALUE_ARRAY=1, + JSON_VALUE_OBJECT=1, + JSON_VALUE_ARRAY=2, JSON_VALUE_STRING, JSON_VALUE_NUMBER, JSON_VALUE_TRUE, diff --git a/mysql-test/r/func_json.result b/mysql-test/r/func_json.result index ae20b8c6849..bae7da495b0 100644 --- a/mysql-test/r/func_json.result +++ b/mysql-test/r/func_json.result @@ -40,6 +40,9 @@ 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_query('{"key1":123, "key1": [1,2,3]}', concat('$', repeat('.k', 1000))); +json_query('{"key1":123, "key1": [1,2,3]}', concat('$', repeat('.k', 1000))) +NULL select json_array(); json_array() [] @@ -289,6 +292,9 @@ json_search(@j, 'all', '10', NULL, '$[*]') select json_search(@j, 'all', '10', NULL, '$[*][0].k'); json_search(@j, 'all', '10', NULL, '$[*][0].k') "$[1][0].k" +select json_search(@j, 'all', '10', NULL, '$**.k'); +json_search(@j, 'all', '10', NULL, '$**.k') +"$[1][0].k" create table t1( json_col text ); insert into t1 values ('{ "a": "foobar" }'), diff --git a/mysql-test/t/func_json.test b/mysql-test/t/func_json.test index 2b0f1cb662e..12e1c742a26 100644 --- a/mysql-test/t/func_json.test +++ b/mysql-test/t/func_json.test @@ -14,6 +14,7 @@ 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_query('{"key1":123, "key1": [1,2,3]}', concat('$', repeat('.k', 1000))); select json_array(); select json_array(1); @@ -120,6 +121,7 @@ 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_search(@j, 'all', '10', NULL, '$**.k'); create table t1( json_col text ); insert into t1 values ('{ "a": "foobar" }'), diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 9978d86e97d..adb0912f1fc 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -1506,7 +1506,7 @@ String *Item_func_json_insert::val_str(String *str) goto error; lp= c_path->p.last_step+1; - if (lp->type == JSON_PATH_ARRAY) + if (lp->type & JSON_PATH_ARRAY) { uint n_item= 0; @@ -1722,7 +1722,7 @@ String *Item_func_json_remove::val_str(String *str) goto error; lp= c_path->p.last_step+1; - if (lp->type == JSON_PATH_ARRAY) + if (lp->type & JSON_PATH_ARRAY) { if (je.value_type != JSON_VALUE_ARRAY) continue; @@ -1982,7 +1982,7 @@ static int append_json_path(String *str, const json_path_t *p) for (c= p->steps+1; c <= p->last_step; c++) { - if (c->type == JSON_PATH_KEY) + if (c->type & JSON_PATH_KEY) { if (str->append(".", 1) || append_simple(str, c->key, c->key_end-c->key)) @@ -2002,6 +2002,7 @@ static int append_json_path(String *str, const json_path_t *p) } +#ifdef DUMMY static int json_path_compare(const json_path_t *a, const json_path_t *b) { uint i, a_len= a->last_step - a->steps, b_len= b->last_step - b->steps; @@ -2014,17 +2015,17 @@ static int json_path_compare(const json_path_t *a, const json_path_t *b) const json_path_step_t *sa= a->steps + i; const json_path_step_t *sb= b->steps + i; - if (sa->type != sb->type) + if (!((sa->type & sb->type) & JSON_PATH_KEY_OR_ARRAY)) return -1; - if (sa->type == JSON_PATH_ARRAY) + if (sa->type & JSON_PATH_ARRAY) { - if (!sa->wild && sa->n_item != sb->n_item) + if (!(sa->type & JSON_PATH_WILD) && sa->n_item != sb->n_item) return -1; } else /* JSON_PATH_KEY */ { - if (!sa->wild && + if (!(sa->type & JSON_PATH_WILD) && (sa->key_end - sa->key != sb->key_end - sb->key || memcmp(sa->key, sb->key, sa->key_end - sa->key) != 0)) return -1; @@ -2033,6 +2034,49 @@ static int json_path_compare(const json_path_t *a, const json_path_t *b) return b_len > a_len; } +#endif /*DUMMY*/ + + +static int json_path_compare(const json_path_t *a, const json_path_t *b) +{ + const json_path_step_t *sa= a->steps + 1; + const json_path_step_t *sb= b->steps + 1; + + if (a->last_step - sa > b->last_step - sb) + return -2; + + while (sa <= a->last_step) + { + if (sb > b->last_step) + return -2; + + if (!((sa->type & sb->type) & JSON_PATH_KEY_OR_ARRAY)) + goto step_failed; + + if (sa->type & JSON_PATH_ARRAY) + { + if (!(sa->type & JSON_PATH_WILD) && sa->n_item != sb->n_item) + goto step_failed; + } + else /* JSON_PATH_KEY */ + { + if (!(sa->type & JSON_PATH_WILD) && + (sa->key_end - sa->key != sb->key_end - sb->key || + memcmp(sa->key, sb->key, sa->key_end - sa->key) != 0)) + goto step_failed; + } + sb++; + sa++; + continue; + +step_failed: + if (!(sa->type & JSON_PATH_DOUBLE_WILD)) + return -1; + sb++; + } + + return sb <= b->last_step; +} static bool path_ok(const json_path_with_flags *paths_list, int n_paths, @@ -2088,8 +2132,7 @@ String *Item_func_json_search::val_str(String *str) (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].type= JSON_PATH_ARRAY_WILD; p.steps[0].n_item= 0; do @@ -2133,27 +2176,21 @@ String *Item_func_json_search::val_str(String *str) if (mode_one) goto end; } - if (p.last_step->type == JSON_PATH_ARRAY) + 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; + p.last_step->type= (enum json_path_step_types) je.value_type; + p.last_step->n_item= 0; } - break; case JST_OBJ_END: case JST_ARRAY_END: p.last_step--; - if (p.last_step->type == JSON_PATH_ARRAY) + if (p.last_step->type & JSON_PATH_ARRAY) p.last_step->n_item++; break; default: diff --git a/strings/json_lib.c b/strings/json_lib.c index 976fab7e7f6..a9498070195 100644 --- a/strings/json_lib.c +++ b/strings/json_lib.c @@ -1006,6 +1006,8 @@ enum json_path_states { PS_KEY, /* Key. */ PS_KNM, /* Parse key name. */ PS_KWD, /* Key wildcard. */ + PS_AST, /* Asterisk. */ + PS_DWD, /* Double wildcard. */ N_PATH_STATES, /* Below are states that aren't in the transitions table. */ PS_SCT, /* Parse the 'strict' keyword. */ PS_EKY, /* '.' after the keyname so next step is the key. */ @@ -1029,7 +1031,7 @@ static int json_path_transitions[N_PATH_STATES][N_PATH_CLASSES]= /* LAX */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, PS_LAX, JE_SYN, PS_GO, JE_SYN, JE_SYN, JE_NOT_JSON_CHR, JE_BAD_CHR}, -/* PT */ { PS_OK, JE_SYN, JE_SYN, PS_AR, JE_SYN, PS_KEY, JE_SYN, JE_SYN, +/* PT */ { PS_OK, JE_SYN, PS_AST, PS_AR, JE_SYN, PS_KEY, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_NOT_JSON_CHR, JE_BAD_CHR}, /* AR */ { JE_EOS, JE_SYN, PS_AWD, JE_SYN, PS_PT, JE_SYN, PS_Z, @@ -1050,11 +1052,17 @@ static int json_path_transitions[N_PATH_STATES][N_PATH_CLASSES]= /* KEY */ { JE_EOS, PS_KNM, PS_KWD, JE_SYN, PS_KNM, JE_SYN, PS_KNM, PS_KNM, PS_KNM, PS_KNM, PS_KNM, JE_SYN, PS_KNM, JE_NOT_JSON_CHR, JE_BAD_CHR}, -/* KNM */ { PS_KOK, PS_KNM, PS_KNM, PS_EAR, PS_KNM, PS_EKY, PS_KNM, +/* KNM */ { PS_KOK, PS_KNM, PS_AST, PS_EAR, PS_KNM, PS_EKY, PS_KNM, PS_KNM, PS_KNM, PS_KNM, PS_KNM, PS_ESC, PS_KNM, JE_NOT_JSON_CHR, JE_BAD_CHR}, /* KWD */ { PS_OK, JE_SYN, JE_SYN, PS_AR, JE_SYN, PS_EKY, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_NOT_JSON_CHR, + JE_BAD_CHR}, +/* AST */ { JE_SYN, JE_SYN, PS_DWD, JE_SYN, JE_SYN, JE_SYN, JE_SYN, + JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_NOT_JSON_CHR, + JE_BAD_CHR}, +/* DWD */ { JE_SYN, JE_SYN, PS_AST, PS_AR, JE_SYN, PS_KEY, JE_SYN, JE_SYN, + JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_NOT_JSON_CHR, JE_BAD_CHR} }; @@ -1063,11 +1071,11 @@ int json_path_setup(json_path_t *p, CHARSET_INFO *i_cs, const uchar *str, const uchar *end) { int c_len, t_next, state= PS_GO; + enum json_path_step_types double_wildcard= JSON_PATH_KEY_NULL; json_string_setup(&p->s, i_cs, str, end); - p->steps[0].type= JSON_PATH_ARRAY; - p->steps[0].wild= 1; + p->steps[0].type= JSON_PATH_ARRAY_WILD; p->last_step= p->steps; p->mode_strict= FALSE; @@ -1096,8 +1104,11 @@ int json_path_setup(json_path_t *p, p->mode_strict= TRUE; state= PS_LAX; continue; + case PS_KWD: case PS_AWD: - p->last_step->wild= 1; + if (p->last_step->type & JSON_PATH_DOUBLE_WILD) + return p->s.error= JE_SYN; + p->last_step->type|= JSON_PATH_WILD; continue; case PS_INT: p->last_step->n_item*= 10; @@ -1109,8 +1120,10 @@ int json_path_setup(json_path_t *p, /* Note no 'continue' here. */ case PS_KEY: p->last_step++; - p->last_step->type= JSON_PATH_KEY; - p->last_step->wild= 0; + if (p->last_step - p->steps >= JSON_DEPTH_LIMIT) + return p->s.error= JE_DEPTH; + p->last_step->type= JSON_PATH_KEY | double_wildcard; + double_wildcard= JSON_PATH_KEY_NULL; p->last_step->key= p->s.c_str; continue; case PS_EAR: @@ -1119,13 +1132,12 @@ int json_path_setup(json_path_t *p, /* Note no 'continue' here. */ case PS_AR: p->last_step++; - p->last_step->type= JSON_PATH_ARRAY; - p->last_step->wild= 0; + if (p->last_step - p->steps >= JSON_DEPTH_LIMIT) + return p->s.error= JE_DEPTH; + p->last_step->type= JSON_PATH_ARRAY | double_wildcard; + double_wildcard= JSON_PATH_KEY_NULL; p->last_step->n_item= 0; continue; - case PS_KWD: - p->last_step->wild= 1; - continue; case PS_ESC: if (json_handle_esc(&p->s)) return 1; @@ -1133,11 +1145,14 @@ int json_path_setup(json_path_t *p, case PS_KOK: p->last_step->key_end= p->s.c_str - c_len; state= PS_OK; - break; + break; /* 'break' as the loop supposed to end after that. */ + case PS_DWD: + double_wildcard= JSON_PATH_DOUBLE_WILD; + continue; }; } while (state != PS_OK); - return 0; + return double_wildcard ? (p->s.error= JE_SYN) : 0; } @@ -1196,7 +1211,8 @@ static int handle_match(json_engine_t *je, json_path_t *p, (*p_cur_step)++; array_counters[*p_cur_step - p->steps]= 0; - if ((int) je->value_type != (int) (*p_cur_step)->type) + if ((int) je->value_type != + (int) ((*p_cur_step)->type & JSON_PATH_KEY_OR_ARRAY)) { (*p_cur_step)--; return json_skip_level(je); @@ -1240,8 +1256,8 @@ int json_find_path(json_engine_t *je, switch (je->state) { case JST_KEY: - DBUG_ASSERT(cur_step->type == JSON_PATH_KEY); - if (!cur_step->wild) + DBUG_ASSERT(cur_step->type & JSON_PATH_KEY); + if (!(cur_step->type & JSON_PATH_WILD)) { json_string_set_str(&key_name, cur_step->key, cur_step->key_end); if (!json_key_matches(je, &key_name)) @@ -1256,8 +1272,8 @@ int json_find_path(json_engine_t *je, goto exit; break; case JST_VALUE: - DBUG_ASSERT(cur_step->type == JSON_PATH_ARRAY); - if (cur_step->wild || + DBUG_ASSERT(cur_step->type & JSON_PATH_ARRAY); + if (cur_step->type & JSON_PATH_WILD || cur_step->n_item == array_counters[cur_step - p->steps]) { /* Array item matches. */ @@ -1316,11 +1332,11 @@ int json_find_paths_next(json_engine_t *je, json_find_paths_t *state) json_path_step_t *cur_step; if (state->path_depths[p_c] < state->cur_depth /* Path already failed. */ || - (cur_step= state->paths[p_c].steps + state->cur_depth)->type != - JSON_PATH_KEY) + !((cur_step= state->paths[p_c].steps + state->cur_depth)->type & + JSON_PATH_KEY)) continue; - if (!cur_step->wild) + if (!(cur_step->type & JSON_PATH_WILD)) { json_string_t key_name; json_string_setup(&key_name, state->paths[p_c].s.cs, @@ -1354,10 +1370,10 @@ int json_find_paths_next(json_engine_t *je, json_find_paths_t *state) { json_path_step_t *cur_step; if (state->path_depths[p_c]< state->cur_depth /* Path already failed. */ || - (cur_step= state->paths[p_c].steps + state->cur_depth)->type != - JSON_PATH_ARRAY) + !((cur_step= state->paths[p_c].steps + state->cur_depth)->type & + JSON_PATH_ARRAY)) continue; - if (cur_step->wild || + if (cur_step->type & JSON_PATH_WILD || cur_step->n_item == state->array_counters[state->cur_depth]) { /* Array item matches. */ @@ -1386,7 +1402,7 @@ int json_find_paths_next(json_engine_t *je, json_find_paths_t *state) if (state->path_depths[p_c] < state->cur_depth) /* Path already failed. */ continue; - if (state->paths[p_c].steps[state->cur_depth].type == + if (state->paths[p_c].steps[state->cur_depth].type & (je->state == JST_OBJ_START) ? JSON_PATH_KEY : JSON_PATH_ARRAY) state->path_depths[p_c]++; } diff --git a/unittest/json_lib/json_lib-t.c b/unittest/json_lib/json_lib-t.c index 49c8f7e34c2..11f02b204f8 100644 --- a/unittest/json_lib/json_lib-t.c +++ b/unittest/json_lib/json_lib-t.c @@ -117,11 +117,11 @@ test_path_parsing() 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[0].type == JSON_PATH_ARRAY_WILD && + p.steps[1].type == JSON_PATH_KEY && 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, + p.steps[3].type == JSON_PATH_KEY_WILD && + p.steps[4].type == JSON_PATH_ARRAY_WILD, "path"); }