MDEV-11453 JSON_CONTAINS returns incorrect values.
The weird logic of json_contains was implemented.
This commit is contained in:
parent
c868acdf65
commit
9320d8ae30
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]");
|
||||
|
@ -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;
|
||||
|
@ -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); }
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user