summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRucha Deodhar <rucha.deodhar@mariadb.com>2021-11-22 22:59:30 +0530
committerRucha Deodhar <rucha.deodhar@mariadb.com>2021-12-16 10:07:34 +0530
commita9235d587ea6637a998e2a885900aaefd51f0a3c (patch)
tree472c084365c792a060f91b659ffbb492d733b081
parent6208228b78917bff13b5dc34428b38596f7404b4 (diff)
downloadmariadb-git-bb-10.8-MDEV-22224.tar.gz
MDEV-22224: Support JSON Path negative indexbb-10.8-MDEV-22224
This patch can be viewed as combination of two parts: 1) Enabling '-' in the path so that the parser does not give out a warning. 2) Setting the negative index to a correct value and returning the appropriate value. 1) To enable using the negative index in the path: To make the parser not return warning when negative index is used in path '-' needs to be allowed in json path characters. P_NEG is added to enable this and is made recognizable by setting the 45th index of json_path_chr_map[] to P_NEG (instead of previous P_ETC) because 45 corresponds to '-' in unicode. When the path is being parsed and '-' is encountered, the parser should recognize it as parsing '-' sign, so a new json state PS_NEG is required. When the state is PS_NEG, it means that a negative integer is going to be parsed so set is_negative_index of current step to 1 and n_item is set accordingly when integer is encountered after '-'. Next proceed with parsing rest of the path and get the correct path. Next thing is parsing the json and returning correct value. 2) Setting the negative index to a correct value and returning the value: While parsing json if we encounter array and the path step for the array is a negative index (n_item < 0), then we can count the number of elements in the array and set n_item to correct corresponding value. This is done in json_skip_array_and_count.
-rw-r--r--include/json_lib.h18
-rw-r--r--mysql-test/main/func_json.result220
-rw-r--r--mysql-test/main/func_json.test207
-rw-r--r--sql/item_jsonfunc.cc107
-rw-r--r--sql/json_table.cc5
-rw-r--r--strings/json_lib.c110
-rw-r--r--unittest/json_lib/json_lib-t.c2
7 files changed, 598 insertions, 71 deletions
diff --git a/include/json_lib.h b/include/json_lib.h
index 6d802860cff..59f5999853e 100644
--- a/include/json_lib.h
+++ b/include/json_lib.h
@@ -83,7 +83,8 @@ enum json_path_step_types
JSON_PATH_KEY_WILD= 1+4,
JSON_PATH_KEY_DOUBLEWILD= 1+8,
JSON_PATH_ARRAY_WILD= 2+4,
- JSON_PATH_ARRAY_DOUBLEWILD= 2+8
+ JSON_PATH_ARRAY_DOUBLEWILD= 2+8,
+ JSON_PATH_NEGATIVE_INDEX= 16
};
@@ -93,7 +94,7 @@ typedef struct st_json_path_step_t
/* 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. */
+ int n_item; /* Item number in an array. No meaning for the key step. */
} json_path_step_t;
@@ -355,7 +356,7 @@ int json_skip_level_and_count(json_engine_t *j, int *n_items_skipped);
*/
int json_find_path(json_engine_t *je,
json_path_t *p, json_path_step_t **p_cur_step,
- uint *array_counters);
+ int *array_counters);
typedef struct st_json_find_paths_t
@@ -364,7 +365,7 @@ typedef struct st_json_find_paths_t
json_path_t *paths;
uint cur_depth;
uint *path_depths;
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
} json_find_paths_t;
@@ -418,13 +419,8 @@ int json_get_path_start(json_engine_t *je, CHARSET_INFO *i_cs,
int json_get_path_next(json_engine_t *je, json_path_t *p);
-
-int json_path_parts_compare(
- const json_path_step_t *a, const json_path_step_t *a_end,
- const json_path_step_t *b, const json_path_step_t *b_end,
- enum json_value_types vt);
int json_path_compare(const json_path_t *a, const json_path_t *b,
- enum json_value_types vt);
+ enum json_value_types vt, const int* array_size_counter);
int json_valid(const char *js, size_t js_len, CHARSET_INFO *cs);
@@ -436,6 +432,8 @@ int json_locate_key(const char *js, const char *js_end,
int json_normalize(DYNAMIC_STRING *result,
const char *s, size_t size, CHARSET_INFO *cs);
+int json_skip_array_and_count(json_engine_t *j, int* n_item);
+
#ifdef __cplusplus
}
#endif
diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result
index 29b996f3066..5174377915b 100644
--- a/mysql-test/main/func_json.result
+++ b/mysql-test/main/func_json.result
@@ -1401,3 +1401,223 @@ DROP TABLE t;
#
# End of 10.5 tests
#
+#
+# Beginning of 10.8 Test
+#
+# MDEV-22224: Support JSON Path negative index
+#
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_ARRAY_APPEND(@json, '$.A[-2][-1]', 5);
+JSON_ARRAY_APPEND(@json, '$.A[-2][-1]', 5)
+{"A": [0, [1, 2, 3], [4, 5, 6], "seven", 0.8, true, false, "eleven", [12, 13, {"key1": "value1"}, [15, 5]], true], "B": {"C": 1}, "D": 2}
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_ARRAY_INSERT(@json, '$.A[-2][-2]', 5);
+JSON_ARRAY_INSERT(@json, '$.A[-2][-2]', 5)
+{"A": [0, [1, 2, 3], [4, 5, 6], "seven", 0.8, true, false, "eleven", [12, 13, {"key1": "value1"}, 5, [15]], true], "B": {"C": 1}, "D": 2}
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_CONTAINS(@json, '15', '$.A[-2][-1]');
+JSON_CONTAINS(@json, '15', '$.A[-2][-1]')
+1
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_CONTAINS_PATH(@json, 'one', '$.A[-2]');
+JSON_CONTAINS_PATH(@json, 'one', '$.A[-2]')
+1
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_EXISTS(@json, '$.A[-2][-1]');
+JSON_EXISTS(@json, '$.A[-2][-1]')
+1
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_EXTRACT(@json, '$.A[-8][1]');
+JSON_EXTRACT(@json, '$.A[-8][1]')
+5
+SET @json= '[{"A": 1, "B": 2, "C": {"D": 3}},{"A": 1, "B": 2, "C": {"D": 3}}]';
+SELECT JSON_KEYS(@json, '$[-1].C');
+JSON_KEYS(@json, '$[-1].C')
+["D"]
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_LENGTH(@json, '$.A[-2][-3]');
+JSON_LENGTH(@json, '$.A[-2][-3]')
+2
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_QUERY(@json, '$.A[-8]');
+JSON_QUERY(@json, '$.A[-8]')
+[4, 5, 6]
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_REMOVE(@json, '$.A[-10]');
+JSON_REMOVE(@json, '$.A[-10]')
+{"A": [[1, 2, 3], [4, 5, 6], "seven", 0.8, true, false, "eleven", [12, [13, 14], {"key1": "value1"}, [15]], true], "B": {"C": 1}, "D": 2}
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_REPLACE(@json, '$.A[-1]', 4);
+JSON_REPLACE(@json, '$.A[-1]', 4)
+{"A": [0, [1, 2, 3], [4, 5, 6], "seven", 0.8, true, false, "eleven", [12, [13, 14], {"key1": "value1"}, [15]], 4], "B": {"C": 1}, "D": 2}
+SET @json = '["abc", [{"k": "10"}, "def"], {"x":"abc"}, {"y":"bcd"}]';
+SELECT JSON_SEARCH(@json, 'all', 'abc', NULL, '$[-2]');
+JSON_SEARCH(@json, 'all', 'abc', NULL, '$[-2]')
+"$[2].x"
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_SET(@json, '$.A[-4]', 100);
+JSON_SET(@json, '$.A[-4]', 100)
+{"A": [0, [1, 2, 3], [4, 5, 6], "seven", 0.8, true, 100, "eleven", [12, [13, 14], {"key1": "value1"}, [15]], true], "B": {"C": 1}, "D": 2}
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":123},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_VALUE(@json, '$.A[-2][-2].key1');
+JSON_VALUE(@json, '$.A[-2][-2].key1')
+123
+#
+# End of 10.8 Test
+#
diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test
index eb84d607430..fd429c578d4 100644
--- a/mysql-test/main/func_json.test
+++ b/mysql-test/main/func_json.test
@@ -894,3 +894,210 @@ DROP TABLE t;
--echo # End of 10.5 tests
--echo #
+--echo #
+--echo # Beginning of 10.8 Test
+--echo #
+--echo # MDEV-22224: Support JSON Path negative index
+--echo #
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_ARRAY_APPEND(@json, '$.A[-2][-1]', 5);
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_ARRAY_INSERT(@json, '$.A[-2][-2]', 5);
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_CONTAINS(@json, '15', '$.A[-2][-1]');
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_CONTAINS_PATH(@json, 'one', '$.A[-2]');
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, 13, {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_EXISTS(@json, '$.A[-2][-1]');
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_EXTRACT(@json, '$.A[-8][1]');
+
+SET @json= '[{"A": 1, "B": 2, "C": {"D": 3}},{"A": 1, "B": 2, "C": {"D": 3}}]';
+SELECT JSON_KEYS(@json, '$[-1].C');
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_LENGTH(@json, '$.A[-2][-3]');
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_QUERY(@json, '$.A[-8]');
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_REMOVE(@json, '$.A[-10]');
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_REPLACE(@json, '$.A[-1]', 4);
+
+SET @json = '["abc", [{"k": "10"}, "def"], {"x":"abc"}, {"y":"bcd"}]';
+SELECT JSON_SEARCH(@json, 'all', 'abc', NULL, '$[-2]');
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":"value1"},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_SET(@json, '$.A[-4]', 100);
+
+SET @json='{
+ "A": [0,
+ [1, 2, 3],
+ [4, 5, 6],
+ "seven",
+ 0.8,
+ true,
+ false,
+ "eleven",
+ [12, [13, 14], {"key1":123},[15]],
+ true],
+ "B": {"C": 1},
+ "D": 2
+ }';
+SELECT JSON_VALUE(@json, '$.A[-2][-2].key1');
+
+--echo #
+--echo # End of 10.8 Test
+--echo #
diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc
index fefb2a1c662..75601354900 100644
--- a/sql/item_jsonfunc.cc
+++ b/sql/item_jsonfunc.cc
@@ -466,7 +466,7 @@ bool Item_func_json_exists::fix_length_and_dec()
longlong Item_func_json_exists::val_int()
{
json_engine_t je;
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
String *js= args[0]->val_json(&tmp_js);
@@ -535,7 +535,7 @@ bool Json_path_extractor::extract(String *str, Item *item_js, Item *item_jp,
{
String *js= item_js->val_json(&tmp_js);
int error= 0;
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
if (!parsed)
{
@@ -796,11 +796,12 @@ bool Item_func_json_extract::fix_length_and_dec()
static bool path_exact(const json_path_with_flags *paths_list, int n_paths,
- const json_path_t *p, json_value_types vt)
+ const json_path_t *p, json_value_types vt,
+ const int *array_size_counter)
{
for (; n_paths > 0; n_paths--, paths_list++)
{
- if (json_path_compare(&paths_list->p, p, vt) == 0)
+ if (json_path_compare(&paths_list->p, p, vt, array_size_counter) == 0)
return TRUE;
}
return FALSE;
@@ -808,11 +809,12 @@ static bool path_exact(const json_path_with_flags *paths_list, int n_paths,
static bool path_ok(const json_path_with_flags *paths_list, int n_paths,
- const json_path_t *p, json_value_types vt)
+ const json_path_t *p, json_value_types vt,
+ const int *array_size_counter)
{
for (; n_paths > 0; n_paths--, paths_list++)
{
- if (json_path_compare(&paths_list->p, p, vt) >= 0)
+ if (json_path_compare(&paths_list->p, p, vt, array_size_counter) >= 0)
return TRUE;
}
return FALSE;
@@ -831,6 +833,8 @@ String *Item_func_json_extract::read_json(String *str,
uint n_arg;
size_t v_len;
int possible_multiple_values;
+ int array_size_counter[JSON_DEPTH_LIMIT];
+ uint has_negative_path= 0;
if ((null_value= args[0]->null_value))
return 0;
@@ -849,6 +853,7 @@ String *Item_func_json_extract::read_json(String *str,
goto return_null;
}
c_path->parsed= c_path->constant;
+ has_negative_path|= c_path->p.types_used & JSON_PATH_NEGATIVE_INDEX;
}
if (args[n_arg]->null_value)
@@ -874,7 +879,12 @@ String *Item_func_json_extract::read_json(String *str,
while (json_get_path_next(&je, &p) == 0)
{
- if (!path_exact(paths, arg_count-1, &p, je.value_type))
+ if (has_negative_path && je.value_type == JSON_VALUE_ARRAY &&
+ json_skip_array_and_count(&je,
+ array_size_counter + (p.last_step - p.steps)))
+ goto error;
+
+ if (!path_exact(paths, arg_count-1, &p, je.value_type, array_size_counter))
continue;
value= je.value_begin;
@@ -1246,7 +1256,7 @@ longlong Item_func_json_contains::val_int()
if (arg_count>2) /* Path specified. */
{
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
if (!path.parsed)
{
String *s_p= args[2]->val_str(&tmp_path);
@@ -1376,7 +1386,7 @@ longlong Item_func_json_contains_path::val_int()
result= !mode_one;
for (n_arg=2; n_arg < arg_count; n_arg++)
{
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
json_path_with_flags *c_path= paths + n_arg - 2;
if (!c_path->parsed)
{
@@ -1437,6 +1447,8 @@ longlong Item_func_json_contains_path::val_int()
json_path_t p;
int n_found;
LINT_INIT(n_found);
+ int array_sizes[JSON_DEPTH_LIMIT];
+ uint has_negative_path= 0;
if ((null_value= args[0]->null_value))
return 0;
@@ -1458,6 +1470,7 @@ longlong Item_func_json_contains_path::val_int()
goto null_return;
}
c_path->parsed= c_path->constant;
+ has_negative_path|= c_path->p.types_used & JSON_PATH_NEGATIVE_INDEX;
}
if (args[n_arg]->null_value)
goto null_return;
@@ -1479,10 +1492,17 @@ longlong Item_func_json_contains_path::val_int()
while (json_get_path_next(&je, &p) == 0)
{
int n_path= arg_count - 2;
+ if (has_negative_path && je.value_type == JSON_VALUE_ARRAY &&
+ json_skip_array_and_count(&je, array_sizes + (p.last_step - p.steps)))
+ {
+ result= 1;
+ break;
+ }
+
json_path_with_flags *c_path= paths;
for (; n_path > 0; n_path--, c_path++)
{
- if (json_path_compare(&c_path->p, &p, je.value_type) >= 0)
+ if (json_path_compare(&c_path->p, &p, je.value_type, array_sizes) >= 0)
{
if (mode_one)
{
@@ -1722,7 +1742,7 @@ String *Item_func_json_array_append::val_str(String *str)
for (n_arg=1, n_path=0; n_arg < arg_count; n_arg+=2, n_path++)
{
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
json_path_with_flags *c_path= paths + n_path;
if (!c_path->parsed)
{
@@ -1850,10 +1870,10 @@ String *Item_func_json_array_insert::val_str(String *str)
for (n_arg=1, n_path=0; n_arg < arg_count; n_arg+=2, n_path++)
{
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
json_path_with_flags *c_path= paths + n_path;
const char *item_pos;
- uint n_item;
+ int n_item, corrected_n_item;
if (!c_path->parsed)
{
@@ -1902,11 +1922,20 @@ String *Item_func_json_array_insert::val_str(String *str)
item_pos= 0;
n_item= 0;
+ corrected_n_item= c_path->p.last_step[1].n_item;
+ if (corrected_n_item < 0)
+ {
+ int array_size;
+ if (json_skip_array_and_count(&je, &array_size))
+ goto js_error;
+ corrected_n_item+= array_size + 1;
+ }
while (json_scan_next(&je) == 0 && je.state != JST_ARRAY_END)
{
DBUG_ASSERT(je.state == JST_VALUE);
- if (n_item == c_path->p.last_step[1].n_item)
+
+ if (n_item == corrected_n_item)
{
item_pos= (const char *) je.s.c_str;
break;
@@ -2632,7 +2661,7 @@ longlong Item_func_json_length::val_int()
String *js= args[0]->val_json(&tmp_js);
json_engine_t je;
uint length= 0;
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
int err;
if ((null_value= args[0]->null_value))
@@ -2859,10 +2888,11 @@ String *Item_func_json_insert::val_str(String *str)
for (n_arg=1, n_path=0; n_arg < arg_count; n_arg+=2, n_path++)
{
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
json_path_with_flags *c_path= paths + n_path;
const char *v_to;
- const json_path_step_t *lp;
+ json_path_step_t *lp;
+ int corrected_n_item;
if (!c_path->parsed)
{
@@ -2907,7 +2937,7 @@ String *Item_func_json_insert::val_str(String *str)
lp= c_path->p.last_step+1;
if (lp->type & JSON_PATH_ARRAY)
{
- uint n_item= 0;
+ int n_item= 0;
if (je.value_type != JSON_VALUE_ARRAY)
{
@@ -2955,13 +2985,21 @@ String *Item_func_json_insert::val_str(String *str)
goto continue_point;
}
+ corrected_n_item= lp->n_item;
+ if (corrected_n_item < 0)
+ {
+ int array_size;
+ if (json_skip_array_and_count(&je, &array_size))
+ goto js_error;
+ corrected_n_item+= array_size;
+ }
while (json_scan_next(&je) == 0 && je.state != JST_ARRAY_END)
{
switch (je.state)
{
case JST_VALUE:
- if (n_item == lp->n_item)
+ if (n_item == corrected_n_item)
goto v_found;
n_item++;
if (json_skip_array_item(&je))
@@ -3110,11 +3148,11 @@ String *Item_func_json_remove::val_str(String *str)
for (n_arg=1, n_path=0; n_arg < arg_count; n_arg++, n_path++)
{
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
json_path_with_flags *c_path= paths + n_path;
const char *rem_start= 0, *rem_end;
- const json_path_step_t *lp;
- uint n_item= 0;
+ json_path_step_t *lp;
+ int n_item= 0;
if (!c_path->parsed)
{
@@ -3158,17 +3196,28 @@ String *Item_func_json_remove::val_str(String *str)
goto js_error;
lp= c_path->p.last_step+1;
+
if (lp->type & JSON_PATH_ARRAY)
{
+ int corrected_n_item;
if (je.value_type != JSON_VALUE_ARRAY)
continue;
+ corrected_n_item= lp->n_item;
+ if (corrected_n_item < 0)
+ {
+ int array_size;
+ if (json_skip_array_and_count(&je, &array_size))
+ goto js_error;
+ corrected_n_item+= array_size;
+ }
+
while (json_scan_next(&je) == 0 && je.state != JST_ARRAY_END)
{
switch (je.state)
{
case JST_VALUE:
- if (n_item == lp->n_item)
+ if (n_item == corrected_n_item)
{
rem_start= (const char *) (je.s.c_str -
(n_item ? je.sav_c_len : 0));
@@ -3320,7 +3369,7 @@ String *Item_func_json_keys::val_str(String *str)
json_engine_t je;
String *js= args[0]->val_json(&tmp_js);
uint n_keys= 0;
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
if ((args[0]->null_value))
goto null_return;
@@ -3527,6 +3576,8 @@ String *Item_func_json_search::val_str(String *str)
json_engine_t je;
json_path_t p, sav_path;
uint n_arg;
+ int array_sizes[JSON_DEPTH_LIMIT];
+ uint has_negative_path= 0;
if (args[0]->null_value || args[2]->null_value)
goto null_return;
@@ -3552,6 +3603,7 @@ String *Item_func_json_search::val_str(String *str)
goto null_return;
}
c_path->parsed= c_path->constant;
+ has_negative_path|= c_path->p.types_used & JSON_PATH_NEGATIVE_INDEX;
}
if (args[n_arg]->null_value)
goto null_return;
@@ -3562,9 +3614,14 @@ String *Item_func_json_search::val_str(String *str)
while (json_get_path_next(&je, &p) == 0)
{
+ if (has_negative_path && je.value_type == JSON_VALUE_ARRAY &&
+ json_skip_array_and_count(&je, array_sizes + (p.last_step - p.steps)))
+ goto js_error;
+
if (json_value_scalar(&je))
{
- if ((arg_count < 5 || path_ok(paths, arg_count - 4, &p, je.value_type)) &&
+ if ((arg_count < 5 ||
+ path_ok(paths, arg_count - 4, &p, je.value_type, array_sizes)) &&
compare_json_value_wild(&je, s_str) != 0)
{
++n_path_found;
diff --git a/sql/json_table.cc b/sql/json_table.cc
index e57dccd00c4..30d59fb06b5 100644
--- a/sql/json_table.cc
+++ b/sql/json_table.cc
@@ -312,7 +312,8 @@ handle_new_nested:
while (!json_get_path_next(&m_engine, &m_cur_path))
{
- if (json_path_compare(&m_path, &m_cur_path, m_engine.value_type))
+ if (json_path_compare(&m_path, &m_cur_path, m_engine.value_type,
+ NULL))
continue;
/* path found. */
++m_ordinality_counter;
@@ -501,7 +502,7 @@ int ha_json_table::fill_column_values(THD *thd, uchar * buf, uchar *pos)
{
json_engine_t je;
json_path_step_t *cur_step;
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
int not_found;
const uchar* node_start;
const uchar* node_end;
diff --git a/strings/json_lib.c b/strings/json_lib.c
index 7b895c216b5..88696abd962 100644
--- a/strings/json_lib.c
+++ b/strings/json_lib.c
@@ -982,6 +982,7 @@ enum json_path_chr_classes {
P_LSQRB, /* [ */
P_RSQRB, /* ] */
P_POINT, /* . */
+ P_NEG, /* hyphen (for negative index in path) */
P_ZERO, /* 0 */
P_DIGIT, /* 123456789 */
P_L, /* l (for "lax") */
@@ -1003,7 +1004,7 @@ static enum json_path_chr_classes json_path_chr_map[128] = {
P_ERR, P_ERR, P_ERR, P_ERR, P_ERR, P_ERR, P_ERR, P_ERR,
P_SPACE, P_ETC, P_QUOTE, P_ETC, P_USD, P_ETC, P_ETC, P_ETC,
- P_ETC, P_ETC, P_ASTER, P_ETC, P_ETC, P_ETC, P_POINT, P_ETC,
+ P_ETC, P_ETC, P_ASTER, P_ETC, P_ETC, P_NEG, P_POINT, P_ETC,
P_ZERO, P_DIGIT, P_DIGIT, P_DIGIT, P_DIGIT, P_DIGIT, P_DIGIT, P_DIGIT,
P_DIGIT, P_DIGIT, P_ETC, P_ETC, P_ETC, P_ETC, P_ETC, P_ETC,
@@ -1026,6 +1027,7 @@ enum json_path_states {
PS_AR, /* Parse array step. */
PS_SAR, /* space after the '['. */
PS_AWD, /* Array wildcard. */
+ PS_NEG, /* Parse '-' (hyphen) */
PS_Z, /* '0' (as an array item number). */
PS_INT, /* Parse integer (as an array item number). */
PS_AS, /* Space. */
@@ -1051,56 +1053,59 @@ enum json_path_states {
static int json_path_transitions[N_PATH_STATES][N_PATH_CLASSES]=
{
/*
- EOS $, * [ ] . 0
+ EOS $, * [ ] . - 0
1..9 L S SPACE \ " ETC
ERR BAD
*/
-/* GO */ { JE_EOS, PS_PT, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN,
+/* GO */ { JE_EOS, PS_PT, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN,
JE_SYN, PS_LAX, PS_SCT, PS_GO, JE_SYN, JE_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* LAX */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN,
+/* LAX */ { JE_EOS, JE_SYN, 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_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* PT */ { PS_OK, JE_SYN, PS_AST, 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_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* AR */ { JE_EOS, JE_SYN, PS_AWD, JE_SYN, JE_SYN, JE_SYN, PS_Z,
+/* AR */ { JE_EOS, JE_SYN, PS_AWD, JE_SYN, JE_SYN, JE_SYN, PS_NEG,PS_Z,
PS_INT, JE_SYN, JE_SYN, PS_SAR, JE_SYN, JE_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* SAR */ { JE_EOS, JE_SYN, PS_AWD, JE_SYN, PS_PT, JE_SYN, PS_Z,
+/* SAR */ { JE_EOS, JE_SYN, PS_AWD, JE_SYN, PS_PT, JE_SYN,JE_SYN, PS_Z,
PS_INT, JE_SYN, JE_SYN, PS_SAR, JE_SYN, JE_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* AWD */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, PS_PT, JE_SYN, JE_SYN,
+/* AWD */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, PS_PT, JE_SYN,JE_SYN, JE_SYN,
JE_SYN, JE_SYN, JE_SYN, PS_AS, JE_SYN, JE_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* Z */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, PS_PT, JE_SYN, JE_SYN,
+/* NEG */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN,JE_SYN,
+ PS_INT, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN, JE_SYN,
+ JE_NOT_JSON_CHR, JE_BAD_CHR},
+/* Z */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, PS_PT, JE_SYN,JE_SYN, JE_SYN,
JE_SYN, JE_SYN, JE_SYN, PS_AS, JE_SYN, JE_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* INT */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, PS_PT, JE_SYN, PS_INT,
+/* INT */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, PS_PT, JE_SYN,JE_SYN, PS_INT,
PS_INT, JE_SYN, JE_SYN, PS_AS, JE_SYN, JE_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
/* AS */ { JE_EOS, JE_SYN, JE_SYN, JE_SYN, PS_PT, JE_SYN, JE_SYN, JE_SYN,
JE_SYN, JE_SYN, PS_AS, JE_SYN, JE_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* KEY */ { JE_EOS, PS_KNM, PS_KWD, JE_SYN, PS_KNM, JE_SYN, PS_KNM,
+/* KEY */ { JE_EOS, PS_KNM, PS_KWD, JE_SYN, PS_KNM, JE_SYN,JE_SYN, PS_KNM,
PS_KNM, PS_KNM, PS_KNM, PS_KNM, JE_SYN, PS_KEYX, PS_KNM,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* KNM */ { PS_KOK, PS_KNM, PS_AST, PS_EAR, PS_KNM, PS_EKY, PS_KNM,
+/* KNM */ { PS_KOK, PS_KNM, PS_AST, PS_EAR, PS_KNM, PS_EKY, JE_SYN,PS_KNM,
PS_KNM, PS_KNM, PS_KNM, PS_KNM, PS_ESC, PS_KNM, PS_KNM,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* KWD */ { PS_OK, JE_SYN, JE_SYN, PS_AR, JE_SYN, PS_EKY, JE_SYN,
+/* 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_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,
+/* 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_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,
+/* 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_SYN, JE_SYN,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* KEYX*/ { JE_EOS, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX,
+/* KEYX*/ { JE_EOS, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX, JE_SYN,PS_KNMX,
PS_KNMX,PS_KNMX, PS_KNMX, PS_KNMX, PS_ESCX, PS_EKYX, PS_KNMX,
JE_NOT_JSON_CHR, JE_BAD_CHR},
-/* KNMX */{ JE_EOS, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX,
+/* KNMX */{ JE_EOS, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX, JE_SYN,PS_KNMX,
PS_KNMX, PS_KNMX, PS_KNMX, PS_KNMX,PS_ESCX, PS_EKYX, PS_KNMX,
JE_NOT_JSON_CHR, JE_BAD_CHR},
};
@@ -1109,7 +1114,7 @@ static int json_path_transitions[N_PATH_STATES][N_PATH_CLASSES]=
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;
+ int c_len, t_next, state= PS_GO, is_negative_index= 0;
enum json_path_step_types double_wildcard= JSON_PATH_KEY_NULL;
json_string_setup(&p->s, i_cs, str, end);
@@ -1151,7 +1156,10 @@ int json_path_setup(json_path_t *p,
continue;
case PS_INT:
p->last_step->n_item*= 10;
- p->last_step->n_item+= p->s.c_next - '0';
+ if (is_negative_index)
+ p->last_step->n_item-= p->s.c_next - '0';
+ else
+ p->last_step->n_item+= p->s.c_next - '0';
continue;
case PS_EKYX:
p->last_step->key_end= p->s.c_str - c_len;
@@ -1163,6 +1171,7 @@ int json_path_setup(json_path_t *p,
/* fall through */
case PS_KEY:
p->last_step++;
+ is_negative_index= 0;
if (p->last_step - p->steps >= JSON_DEPTH_LIMIT)
return p->s.error= JE_DEPTH;
p->types_used|= p->last_step->type= JSON_PATH_KEY | double_wildcard;
@@ -1182,6 +1191,7 @@ int json_path_setup(json_path_t *p,
p->types_used|= p->last_step->type= JSON_PATH_ARRAY | double_wildcard;
double_wildcard= JSON_PATH_KEY_NULL;
p->last_step->n_item= 0;
+ is_negative_index= 0;
continue;
case PS_ESC:
if (json_handle_esc(&p->s))
@@ -1200,6 +1210,9 @@ int json_path_setup(json_path_t *p,
case PS_DWD:
double_wildcard= JSON_PATH_DOUBLE_WILD;
continue;
+ case PS_NEG:
+ p->types_used|= JSON_PATH_NEGATIVE_INDEX;
+ is_negative_index= 1;
};
} while (state != PS_OK);
@@ -1239,6 +1252,15 @@ int json_skip_level_and_count(json_engine_t *j, int *n_items_skipped)
}
+int json_skip_array_and_count(json_engine_t *je, int *n_items)
+{
+ json_engine_t j= *je;
+ *n_items= 0;
+
+ return json_skip_level_and_count(&j, n_items);
+}
+
+
int json_skip_key(json_engine_t *j)
{
if (json_read_value(j))
@@ -1251,7 +1273,7 @@ int json_skip_key(json_engine_t *j)
}
-#define SKIPPED_STEP_MARK ((uint) ~0)
+#define SKIPPED_STEP_MARK ((int) ~0)
/*
Current step of the patch matches the JSON construction.
@@ -1259,7 +1281,7 @@ int json_skip_key(json_engine_t *j)
step of the path.
*/
static int handle_match(json_engine_t *je, json_path_t *p,
- json_path_step_t **p_cur_step, uint *array_counters)
+ json_path_step_t **p_cur_step, int *array_counters)
{
json_path_step_t *next_step= *p_cur_step + 1;
@@ -1296,13 +1318,28 @@ static int handle_match(json_engine_t *je, json_path_t *p,
} while (next_step->type == JSON_PATH_ARRAY && next_step->n_item == 0);
}
-
- array_counters[next_step - p->steps]= 0;
-
if ((int) je->value_type !=
(int) (next_step->type & JSON_PATH_KEY_OR_ARRAY))
return json_skip_level(je);
+ if (next_step->type == JSON_PATH_ARRAY)
+ {
+ int array_size;
+ if (next_step->n_item >= 0)
+ array_size= 0;
+ else
+ {
+ json_engine_t j2= *je;
+ if (json_skip_array_and_count(&j2, &array_size))
+ {
+ *je= j2;
+ return 1;
+ }
+ array_size= -array_size;
+ }
+ array_counters[next_step - p->steps]= array_size;
+ }
+
*p_cur_step= next_step;
return 0;
}
@@ -1327,7 +1364,7 @@ int json_key_matches(json_engine_t *je, json_string_t *k)
int json_find_path(json_engine_t *je,
json_path_t *p, json_path_step_t **p_cur_step,
- uint *array_counters)
+ int *array_counters)
{
json_string_t key_name;
@@ -1770,9 +1807,10 @@ int json_get_path_next(json_engine_t *je, json_path_t *p)
int json_path_parts_compare(
const json_path_step_t *a, const json_path_step_t *a_end,
const json_path_step_t *b, const json_path_step_t *b_end,
- enum json_value_types vt)
+ enum json_value_types vt, const int *array_sizes)
{
int res, res2;
+ const json_path_step_t *temp_b= b;
while (a <= a_end)
{
@@ -1795,7 +1833,9 @@ int json_path_parts_compare(
{
if (b->type & JSON_PATH_ARRAY)
{
- if ((a->type & JSON_PATH_WILD) || a->n_item == b->n_item)
+ if ((a->type & JSON_PATH_WILD) ||
+ (a->n_item >= 0 ? a->n_item == b->n_item :
+ a->n_item == b->n_item - array_sizes[b-temp_b]))
goto step_fits;
goto step_failed;
}
@@ -1830,11 +1870,13 @@ step_fits:
}
/* Double wild handling needs recursions. */
- res= json_path_parts_compare(a+1, a_end, b, b_end, vt);
+ res= json_path_parts_compare(a+1, a_end, b, b_end, vt,
+ array_sizes + (b - temp_b));
if (res == 0)
return 0;
- res2= json_path_parts_compare(a, a_end, b, b_end, vt);
+ res2= json_path_parts_compare(a, a_end, b, b_end, vt,
+ array_sizes + (b - temp_b));
return (res2 >= 0) ? res2 : res;
@@ -1846,11 +1888,13 @@ step_fits_autowrap:
}
/* Double wild handling needs recursions. */
- res= json_path_parts_compare(a+1, a_end, b+1, b_end, vt);
+ res= json_path_parts_compare(a+1, a_end, b+1, b_end, vt,
+ array_sizes + (b - temp_b));
if (res == 0)
return 0;
- res2= json_path_parts_compare(a, a_end, b+1, b_end, vt);
+ res2= json_path_parts_compare(a, a_end, b+1, b_end, vt,
+ array_sizes + (b - temp_b));
return (res2 >= 0) ? res2 : res;
@@ -1861,10 +1905,10 @@ step_fits_autowrap:
int json_path_compare(const json_path_t *a, const json_path_t *b,
- enum json_value_types vt)
+ enum json_value_types vt, const int *array_size)
{
return json_path_parts_compare(a->steps+1, a->last_step,
- b->steps+1, b->last_step, vt);
+ b->steps+1, b->last_step, vt, array_size);
}
diff --git a/unittest/json_lib/json_lib-t.c b/unittest/json_lib/json_lib-t.c
index 378ebe201f5..c5a6df54e1d 100644
--- a/unittest/json_lib/json_lib-t.c
+++ b/unittest/json_lib/json_lib-t.c
@@ -140,7 +140,7 @@ test_search()
json_path_t p;
json_path_step_t *cur_step;
int n_matches, scal_values;
- uint array_counters[JSON_DEPTH_LIMIT];
+ int array_counters[JSON_DEPTH_LIMIT];
if (json_scan_start(&je, ci, s_e(fj0)) ||
json_path_setup(&p, ci, s_e(fp0)))