mirror of
https://github.com/MariaDB/server.git
synced 2025-08-08 11:22:35 +03:00
MDEV-11453 JSON_CONTAINS returns incorrect values.
The weird logic of json_contains was implemented.
This commit is contained in:
@@ -311,6 +311,7 @@ int json_skip_level(json_engine_t *j);
|
|||||||
*/
|
*/
|
||||||
#define json_value_scalar(je) ((je)->value_type > JSON_VALUE_ARRAY)
|
#define json_value_scalar(je) ((je)->value_type > JSON_VALUE_ARRAY)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Look for the JSON PATH in the json string.
|
Look for the JSON PATH in the json string.
|
||||||
Function can be called several times with same JSON/PATH to
|
Function can be called several times with same JSON/PATH to
|
||||||
|
@@ -90,6 +90,57 @@ json_contains('"youth"', '"you"')
|
|||||||
0
|
0
|
||||||
select json_contains('[1]', '[1]', '$', '$[0]');
|
select json_contains('[1]', '[1]', '$', '$[0]');
|
||||||
ERROR 42000: Incorrect parameter count in the call to native function 'json_contains'
|
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]");
|
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]");
|
||||||
json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]")
|
json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[1]")
|
||||||
1
|
1
|
||||||
|
@@ -36,6 +36,23 @@ select json_contains('"you"', '"you"');
|
|||||||
select json_contains('"youth"', '"you"');
|
select json_contains('"youth"', '"you"');
|
||||||
--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT
|
--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT
|
||||||
select json_contains('[1]', '[1]', '$', '$[0]');
|
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[1]");
|
||||||
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[10]");
|
select json_contains_path('{"key1":1, "key2":[2,3]}', "oNE", "$.key2[10]");
|
||||||
|
@@ -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()
|
void Item_func_json_contains::fix_length_and_dec()
|
||||||
{
|
{
|
||||||
a2_constant= args[1]->const_item();
|
a2_constant= args[1]->const_item();
|
||||||
a2_parsed= FALSE;
|
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();
|
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--)
|
DBUG_ASSERT(j->state == JST_KEY);
|
||||||
tmp_paths[i-1].free();
|
if (json_key_matches(j, key))
|
||||||
tmp_paths= 0;
|
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()
|
longlong Item_func_json_contains::val_int()
|
||||||
{
|
{
|
||||||
String *js= args[0]->val_str(&tmp_js);
|
String *js= args[0]->val_str(&tmp_js);
|
||||||
json_engine_t je;
|
json_engine_t je, ve;
|
||||||
uint n_arg;
|
int result;
|
||||||
|
|
||||||
if ((null_value= args[0]->null_value))
|
if ((null_value= args[0]->null_value))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -648,54 +771,37 @@ longlong Item_func_json_contains::val_int()
|
|||||||
json_scan_start(&je, js->charset(),(const uchar *) js->ptr(),
|
json_scan_start(&je, js->charset(),(const uchar *) js->ptr(),
|
||||||
(const uchar *) js->ptr() + js->length());
|
(const uchar *) js->ptr() + js->length());
|
||||||
|
|
||||||
if (arg_count<3) /* No path specified. */
|
if (arg_count>2) /* 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++)
|
|
||||||
{
|
{
|
||||||
uint array_counters[JSON_DEPTH_LIMIT];
|
uint array_counters[JSON_DEPTH_LIMIT];
|
||||||
json_path_with_flags *c_path= paths + n_arg - 2;
|
if (!path.parsed)
|
||||||
if (!c_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 &&
|
if (s_p &&
|
||||||
json_path_setup(&c_path->p,s_p->charset(),(const uchar *) s_p->ptr(),
|
json_path_setup(&path.p,s_p->charset(),(const uchar *) s_p->ptr(),
|
||||||
(const uchar *) s_p->ptr() + s_p->length()))
|
(const uchar *) s_p->end()))
|
||||||
goto error;
|
goto error;
|
||||||
c_path->parsed= c_path->constant;
|
path.parsed= path.constant;
|
||||||
}
|
}
|
||||||
|
if (args[2]->null_value)
|
||||||
if (args[n_arg]->null_value)
|
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
json_scan_start(&je, js->charset(),(const uchar *) js->ptr(),
|
path.cur_step= path.p.steps;
|
||||||
(const uchar *) js->ptr() + js->length());
|
if (json_find_path(&je, &path.p, &path.cur_step, array_counters))
|
||||||
|
|
||||||
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))
|
|
||||||
goto error;
|
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:
|
error:
|
||||||
null_value= 1;
|
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)
|
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 *sa= a->steps + 1;
|
||||||
|
@@ -174,17 +174,15 @@ class Item_func_json_contains: public Item_int_func
|
|||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
String tmp_js;
|
String tmp_js;
|
||||||
json_path_with_flags *paths;
|
json_path_with_flags path;
|
||||||
String *tmp_paths;
|
String tmp_path;
|
||||||
bool a2_constant, a2_parsed;
|
bool a2_constant, a2_parsed;
|
||||||
String tmp_val, *val;
|
String tmp_val, *val;
|
||||||
public:
|
public:
|
||||||
Item_func_json_contains(THD *thd, List<Item> &list):
|
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"; }
|
const char *func_name() const { return "json_contains"; }
|
||||||
bool fix_fields(THD *thd, Item **ref);
|
|
||||||
void fix_length_and_dec();
|
void fix_length_and_dec();
|
||||||
void cleanup();
|
|
||||||
longlong val_int();
|
longlong val_int();
|
||||||
Item *get_copy(THD *thd, MEM_ROOT *mem_root)
|
Item *get_copy(THD *thd, MEM_ROOT *mem_root)
|
||||||
{ return get_item_copy<Item_func_json_contains>(thd, mem_root, this); }
|
{ return get_item_copy<Item_func_json_contains>(thd, mem_root, this); }
|
||||||
|
@@ -1182,6 +1182,13 @@ int json_skip_level(json_engine_t *j)
|
|||||||
|
|
||||||
int json_skip_key(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))
|
if (json_read_value(j))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user