summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Botchkov <holyfoot@askmonty.org>2016-12-11 01:12:33 +0400
committerAlexey Botchkov <holyfoot@askmonty.org>2016-12-11 01:12:33 +0400
commit9320d8ae30c18420bef659618175836221d363ea (patch)
treedc2b4fba02c479b5671327c4f27557442444dbc8
parentc868acdf656213cdc081c4c965a1bcf3d22558bb (diff)
downloadmariadb-git-9320d8ae30c18420bef659618175836221d363ea.tar.gz
MDEV-11453 JSON_CONTAINS returns incorrect values.
The weird logic of json_contains was implemented.
-rw-r--r--include/json_lib.h1
-rw-r--r--mysql-test/r/func_json.result51
-rw-r--r--mysql-test/t/func_json.test17
-rw-r--r--sql/item_jsonfunc.cc245
-rw-r--r--sql/item_jsonfunc.h8
-rw-r--r--strings/json_lib.c7
6 files changed, 237 insertions, 92 deletions
diff --git a/include/json_lib.h b/include/json_lib.h
index b2ea39cd728..abef3e60db6 100644
--- a/include/json_lib.h
+++ b/include/json_lib.h
@@ -311,6 +311,7 @@ int json_skip_level(json_engine_t *j);
*/
#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
diff --git a/mysql-test/r/func_json.result b/mysql-test/r/func_json.result
index bae7da495b0..c972057e37c 100644
--- a/mysql-test/r/func_json.result
+++ b/mysql-test/r/func_json.result
@@ -90,6 +90,57 @@ json_contains('"youth"', '"you"')
0
select json_contains('[1]', '[1]', '$', '$[0]');
ERROR 42000: Incorrect parameter count in the call to native function 'json_contains'
+select json_contains('', '', '$');
+json_contains('', '', '$')
+0
+select json_contains('null', 'null', '$');
+json_contains('null', 'null', '$')
+1
+select json_contains('"10"', '"10"', '$');
+json_contains('"10"', '"10"', '$')
+1
+select json_contains('"10"', '10', '$');
+json_contains('"10"', '10', '$')
+0
+select json_contains('10.1', '10', '$');
+json_contains('10.1', '10', '$')
+0
+select json_contains('10.0', '10', '$');
+json_contains('10.0', '10', '$')
+1
+select json_contains('[1]', '1');
+json_contains('[1]', '1')
+1
+select json_contains('[2, 1]', '1');
+json_contains('[2, 1]', '1')
+1
+select json_contains('[2, [2, 3], 1]', '1');
+json_contains('[2, [2, 3], 1]', '1')
+1
+select json_contains('[4, [2, 3], 1]', '2');
+json_contains('[4, [2, 3], 1]', '2')
+1
+select json_contains('[2, 1]', '[1, 2]');
+json_contains('[2, 1]', '[1, 2]')
+1
+select json_contains('[2, 1]', '[1, 0, 2]');
+json_contains('[2, 1]', '[1, 0, 2]')
+0
+select json_contains('[2, 0, 3, 1]', '[1, 2]');
+json_contains('[2, 0, 3, 1]', '[1, 2]')
+1
+select json_contains('{"b":[1,2], "a":1}', '{"a":1, "b":2}');
+json_contains('{"b":[1,2], "a":1}', '{"a":1, "b":2}')
+1
+select json_contains('{"a":1}', '{}');
+json_contains('{"a":1}', '{}')
+1
+select json_contains('[1, {"a":1}]', '{}');
+json_contains('[1, {"a":1}]', '{}')
+1
+select json_contains('[1, {"a":1}]', '{"a":1}');
+json_contains('[1, {"a":1}]', '{"a":1}')
+1
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]");
json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]")
1
diff --git a/mysql-test/t/func_json.test b/mysql-test/t/func_json.test
index 12e1c742a26..d3d75fa3913 100644
--- a/mysql-test/t/func_json.test
+++ b/mysql-test/t/func_json.test
@@ -36,6 +36,23 @@ select json_contains('"you"', '"you"');
select json_contains('"youth"', '"you"');
--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT
select json_contains('[1]', '[1]', '$', '$[0]');
+select json_contains('', '', '$');
+select json_contains('null', 'null', '$');
+select json_contains('"10"', '"10"', '$');
+select json_contains('"10"', '10', '$');
+select json_contains('10.1', '10', '$');
+select json_contains('10.0', '10', '$');
+select json_contains('[1]', '1');
+select json_contains('[2, 1]', '1');
+select json_contains('[2, [2, 3], 1]', '1');
+select json_contains('[4, [2, 3], 1]', '2');
+select json_contains('[2, 1]', '[1, 2]');
+select json_contains('[2, 1]', '[1, 0, 2]');
+select json_contains('[2, 0, 3, 1]', '[1, 2]');
+select json_contains('{"b":[1,2], "a":1}', '{"a":1, "b":2}');
+select json_contains('{"a":1}', '{}');
+select json_contains('[1, {"a":1}]', '{}');
+select json_contains('[1, {"a":1}]', '{"a":1}');
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]");
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[10]");
diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc
index adb0912f1fc..e3fa34cd72c 100644
--- a/sql/item_jsonfunc.cc
+++ b/sql/item_jsonfunc.cc
@@ -596,39 +596,162 @@ error:
}
-bool Item_func_json_contains::fix_fields(THD *thd, Item **ref)
-{
- return alloc_tmp_paths(thd, arg_count-2, &paths, &tmp_paths) ||
- Item_int_func::fix_fields(thd, ref);
-}
-
-
void Item_func_json_contains::fix_length_and_dec()
{
a2_constant= args[1]->const_item();
a2_parsed= FALSE;
- mark_constant_paths(paths, args+2, arg_count-2);
+ if (arg_count > 2)
+ path.set_constant_flag(args[2]->const_item());
Item_int_func::fix_length_and_dec();
}
-void Item_func_json_contains::cleanup()
+static int find_key_in_object(json_engine_t *j, json_string_t *key)
{
- if (tmp_paths)
+ const uchar *c_str= key->c_str;
+
+ while (json_scan_next(j) == 0 && j->state != JST_OBJ_END)
{
- for (uint i= arg_count-2; i>0; i--)
- tmp_paths[i-1].free();
- tmp_paths= 0;
+ DBUG_ASSERT(j->state == JST_KEY);
+ if (json_key_matches(j, key))
+ return TRUE;
+ if (json_skip_key(j))
+ return FALSE;
+ key->c_str= c_str;
}
- Item_int_func::cleanup();
+
+ return FALSE;
+}
+
+
+static int check_contains(json_engine_t *js, json_engine_t *value)
+{
+ json_engine_t loc_js;
+ bool set_js;
+
+ switch (js->value_type)
+ {
+ case JSON_VALUE_OBJECT:
+ {
+ json_string_t key_name;
+
+ if (value->value_type != JSON_VALUE_OBJECT)
+ return FALSE;
+
+ loc_js= *js;
+ set_js= FALSE;
+ json_string_set_cs(&key_name, value->s.cs);
+ while (json_scan_next(value) == 0 && value->state != JST_OBJ_END)
+ {
+ const uchar *k_start, *k_end;
+
+ DBUG_ASSERT(value->state == JST_KEY);
+ k_start= value->s.c_str;
+ while (json_read_keyname_chr(value) == 0)
+ k_end= value->s.c_str;
+
+ if (value->s.error || json_read_value(value))
+ return FALSE;
+
+ if (set_js)
+ *js= loc_js;
+ else
+ set_js= TRUE;
+
+ json_string_set_str(&key_name, k_start, k_end);
+ if (!find_key_in_object(js, &key_name) ||
+ json_read_value(js) ||
+ !check_contains(js, value))
+ return FALSE;
+ }
+
+ return value->state == JST_OBJ_END && !json_skip_level(js);
+ }
+ case JSON_VALUE_ARRAY:
+ if (value->value_type != JSON_VALUE_ARRAY)
+ {
+ while (json_scan_next(js) == 0 && js->state != JST_ARRAY_END)
+ {
+ DBUG_ASSERT(js->state == JST_VALUE);
+ if (json_read_value(js))
+ return FALSE;
+
+ if (check_contains(js, value))
+ {
+ if (json_skip_level(js))
+ return FALSE;
+ return TRUE;
+ }
+ if (value->s.error || js->s.error)
+ return FALSE;
+ }
+ return FALSE;
+ }
+ /* else */
+ loc_js= *js;
+ set_js= FALSE;
+ while (json_scan_next(value) == 0 && value->state != JST_ARRAY_END)
+ {
+ DBUG_ASSERT(value->state == JST_VALUE);
+ if (json_read_value(value))
+ return FALSE;
+
+ if (set_js)
+ *js= loc_js;
+ else
+ set_js= TRUE;
+ if (!check_contains(js, value))
+ return FALSE;
+ }
+
+ return value->state == JST_ARRAY_END;
+
+ case JSON_VALUE_STRING:
+ if (value->value_type != JSON_VALUE_STRING)
+ return FALSE;
+ /*
+ TODO: make proper json-json comparison here that takes excapint
+ into account.
+ */
+ return value->value_len == js->value_len &&
+ memcmp(value->value, js->value, value->value_len) == 0;
+ case JSON_VALUE_NUMBER:
+ if (value->value_type == JSON_VALUE_NUMBER)
+ {
+ double d_j, d_v;
+ char *end;
+ int err;
+
+ d_j= my_strntod(js->s.cs, (char *) js->value, js->value_len,
+ &end, &err);;
+ d_v= my_strntod(value->s.cs, (char *) value->value, value->value_len,
+ &end, &err);;
+
+ return (fabs(d_j - d_v) < 1e-12);
+ }
+ else
+ return FALSE;
+
+ default:
+ break;
+ }
+
+ /*
+ We have these not mentioned in the 'switch' above:
+
+ case JSON_VALUE_TRUE:
+ case JSON_VALUE_FALSE:
+ case JSON_VALUE_NULL:
+ */
+ return value->value_type == js->value_type;
}
longlong Item_func_json_contains::val_int()
{
String *js= args[0]->val_str(&tmp_js);
- json_engine_t je;
- uint n_arg;
+ json_engine_t je, ve;
+ int result;
if ((null_value= args[0]->null_value))
return 0;
@@ -648,54 +771,37 @@ longlong Item_func_json_contains::val_int()
json_scan_start(&je, js->charset(),(const uchar *) js->ptr(),
(const uchar *) js->ptr() + js->length());
- if (arg_count<3) /* No path specified. */
- {
- if (json_read_value(&je))
- goto error;
- String jv_str((const char *)je.value_begin,
- je.value_end - je.value_begin, js->charset());
- return val->eq(&jv_str, js->charset());
- }
-
- for (n_arg=2; n_arg < arg_count; n_arg++)
+ if (arg_count>2) /* Path specified. */
{
uint array_counters[JSON_DEPTH_LIMIT];
- json_path_with_flags *c_path= paths + n_arg - 2;
- if (!c_path->parsed)
+ if (!path.parsed)
{
- String *s_p= args[n_arg]->val_str(tmp_paths+(n_arg-2));
+ String *s_p= args[2]->val_str(&tmp_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()))
+ json_path_setup(&path.p,s_p->charset(),(const uchar *) s_p->ptr(),
+ (const uchar *) s_p->end()))
goto error;
- c_path->parsed= c_path->constant;
+ path.parsed= path.constant;
}
-
- if (args[n_arg]->null_value)
+ if (args[2]->null_value)
goto error;
- 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))
- {
- /* Path wasn't found. */
- if (je.s.error)
- goto error;
- continue;
- }
-
- if (json_read_value(&je))
+ path.cur_step= path.p.steps;
+ if (json_find_path(&je, &path.p, &path.cur_step, array_counters))
goto error;
- String jv_str((const char *)je.value_begin,
- je.value_end - je.value_begin, js->charset());
- if (val->eq(&jv_str, js->charset()))
- return 1;
}
+ json_scan_start(&ve, val->charset(),(const uchar *) val->ptr(),
+ (const uchar *) val->end());
- return 0;
+ if (json_read_value(&je) || json_read_value(&ve))
+ return FALSE;
+
+ result= check_contains(&je, &ve);
+ if (je.s.error || ve.s.error)
+ goto error;
+
+ return result;
error:
null_value= 1;
@@ -2002,41 +2108,6 @@ 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;
-
- 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) & JSON_PATH_KEY_OR_ARRAY))
- return -1;
-
- if (sa->type & JSON_PATH_ARRAY)
- {
- if (!(sa->type & JSON_PATH_WILD) && sa->n_item != sb->n_item)
- return -1;
- }
- 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))
- return -1;
- }
- }
-
- 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;
diff --git a/sql/item_jsonfunc.h b/sql/item_jsonfunc.h
index da9baf47648..e6f26d95b13 100644
--- a/sql/item_jsonfunc.h
+++ b/sql/item_jsonfunc.h
@@ -174,17 +174,15 @@ class Item_func_json_contains: public Item_int_func
{
protected:
String tmp_js;
- json_path_with_flags *paths;
- String *tmp_paths;
+ json_path_with_flags path;
+ String tmp_path;
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) {}
+ Item_int_func(thd, list) {}
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); }
diff --git a/strings/json_lib.c b/strings/json_lib.c
index a9498070195..015ce1f39f8 100644
--- a/strings/json_lib.c
+++ b/strings/json_lib.c
@@ -1182,6 +1182,13 @@ int json_skip_level(json_engine_t *j)
int json_skip_key(json_engine_t *j)
{
+ if (j->state == JST_KEY)
+ {
+ while (json_read_keyname_chr(j) == 0);
+ if (j->s.error)
+ return 1;
+ }
+
if (json_read_value(j))
return 1;