From dd9070dbf07ff326394e65620679c96282412fdf Mon Sep 17 00:00:00 2001 From: kjarir Date: Fri, 3 Apr 2026 19:21:47 +0530 Subject: [PATCH] MDEV-13594: Implement -> and ->> JSON path operators --- mysql-test/suite/json/r/mdev13594.result | 35 +++++++++++++++++ mysql-test/suite/json/t/mdev13594.test | 33 ++++++++++++++++ sql/sql_lex.cc | 10 +++++ sql/sql_yacc.yy | 49 ++++++++++++++++++++++-- 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 mysql-test/suite/json/r/mdev13594.result create mode 100644 mysql-test/suite/json/t/mdev13594.test diff --git a/mysql-test/suite/json/r/mdev13594.result b/mysql-test/suite/json/r/mdev13594.result new file mode 100644 index 0000000000000..c223a70d6c411 --- /dev/null +++ b/mysql-test/suite/json/r/mdev13594.result @@ -0,0 +1,35 @@ +# MDEV-13594: Support for JSON operators -> and ->> +CREATE TABLE t1 (id INT, data JSON); +INSERT INTO t1 VALUES (1, '{"name":"Alice","age":30}'); +INSERT INTO t1 VALUES (2, '{"name":"Bob","age":25}'); +SELECT data->'$.name' FROM t1; +data->'$.name' +"Alice" +"Bob" +SELECT data->>'$.name' FROM t1; +data->>'$.name' +Alice +Bob +SELECT id FROM t1 WHERE data->>'$.name' = 'Alice'; +id +1 +SELECT data->'$.age' FROM t1; +data->'$.age' +30 +25 +SELECT id FROM t1 ORDER BY data->>'$.name'; +id +1 +2 +SELECT '{"x":1}'->'$.x'; +'{"x":1}'->'$.x' +1 +SELECT +data->'$.name', +JSON_EXTRACT(data, '$.name'), +data->>'$.name', +JSON_UNQUOTE(JSON_EXTRACT(data, '$.name')) +FROM t1 LIMIT 1; +data->'$.name' JSON_EXTRACT(data, '$.name') data->>'$.name' JSON_UNQUOTE(JSON_EXTRACT(data, '$.name')) +"Alice" "Alice" Alice Alice +DROP TABLE t1; diff --git a/mysql-test/suite/json/t/mdev13594.test b/mysql-test/suite/json/t/mdev13594.test new file mode 100644 index 0000000000000..e70bd6f5158ab --- /dev/null +++ b/mysql-test/suite/json/t/mdev13594.test @@ -0,0 +1,33 @@ +--echo # MDEV-13594: Support for JSON operators -> and ->> + +CREATE TABLE t1 (id INT, data JSON); +INSERT INTO t1 VALUES (1, '{"name":"Alice","age":30}'); +INSERT INTO t1 VALUES (2, '{"name":"Bob","age":25}'); + +# Test -> operator (JSON_EXTRACT equivalent) +SELECT data->'$.name' FROM t1; + +# Test ->> operator (JSON_UNQUOTE(JSON_EXTRACT) equivalent) +SELECT data->>'$.name' FROM t1; + +# Test in WHERE clause +SELECT id FROM t1 WHERE data->>'$.name' = 'Alice'; + +# Test with numeric path +SELECT data->'$.age' FROM t1; + +# Test in ORDER BY +SELECT id FROM t1 ORDER BY data->>'$.name'; + +# Test with literal JSON string (not just column) +SELECT '{"x":1}'->'$.x'; + +# Verify equivalence +SELECT + data->'$.name', + JSON_EXTRACT(data, '$.name'), + data->>'$.name', + JSON_UNQUOTE(JSON_EXTRACT(data, '$.name')) +FROM t1 LIMIT 1; + +DROP TABLE t1; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 2ced66caa5cc2..066e49106d0e3 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2091,6 +2091,16 @@ int Lex_input_stream::lex_one_token(YYSTYPE *yylval, THD *thd) return((int) c); case MY_LEX_MINUS_OR_COMMENT: + if (yyPeek() == '>') + { + yySkip(); + if (yyPeek() == '>') + { + yySkip(); + return JSON_UNQUOTED_SEPARATOR_SYM; + } + return JSON_SEPARATOR_SYM; + } if (yyPeek() == '-' && (my_isspace(cs,yyPeekn(1)) || my_iscntrl(cs,yyPeekn(1)))) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c7072acefc7ff..011f207710be1 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -386,9 +386,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); */ %ifdef MARIADB -%expect 70 +%expect 72 %else -%expect 71 +%expect 73 %endif /* @@ -474,6 +474,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token SHIFT_LEFT /* OPERATOR */ %token SHIFT_RIGHT /* OPERATOR */ %token ARROW_SYM /* OPERATOR */ +%token JSON_SEPARATOR_SYM /* OPERATOR */ +%token JSON_UNQUOTED_SEPARATOR_SYM /* OPERATOR */ /* @@ -1232,6 +1234,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %left SHIFT_LEFT SHIFT_RIGHT %left '-' '+' ORACLE_CONCAT_SYM %left '*' '/' '%' DIV_SYM MOD_SYM +%left JSON_SEPARATOR_SYM JSON_UNQUOTED_SEPARATOR_SYM %left '^' %left MYSQL_CONCAT_SYM /* @@ -1592,7 +1595,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); boolean_test predicate bit_expr parenthesized_expr table_wild simple_expr column_default_non_parenthesized_expr udf_expr - primary_expr string_factor_expr mysql_concatenation_expr + primary_expr primary_base_expr string_factor_expr mysql_concatenation_expr select_sublist_qualified_asterisk expr_or_ignore expr_or_ignore_or_default signed_literal expr_or_literal @@ -10612,7 +10615,7 @@ column_default_non_parenthesized_expr: } ; -primary_expr: +primary_base_expr: column_default_non_parenthesized_expr | explicit_cursor_attr | '(' parenthesized_expr ')' { $$= $2; } @@ -10623,6 +10626,44 @@ primary_expr: } ; +primary_expr: + primary_base_expr + | primary_base_expr JSON_SEPARATOR_SYM TEXT_STRING + { + Item *path= new (thd->mem_root) Item_string(thd, $3.str, $3.length, + thd->variables.collation_connection); + if (unlikely(path == NULL)) + MYSQL_YYABORT; + List *list= new (thd->mem_root) List; + if (unlikely(list == NULL || + list->push_back($1, thd->mem_root) || + list->push_back(path, thd->mem_root))) + MYSQL_YYABORT; + $$= new (thd->mem_root) Item_func_json_extract(thd, *list); + if (unlikely($$ == NULL)) + MYSQL_YYABORT; + } + | primary_base_expr JSON_UNQUOTED_SEPARATOR_SYM TEXT_STRING + { + Item *path= new (thd->mem_root) Item_string(thd, $3.str, $3.length, + thd->variables.collation_connection); + if (unlikely(path == NULL)) + MYSQL_YYABORT; + List *list= new (thd->mem_root) List; + if (unlikely(list == NULL || + list->push_back($1, thd->mem_root) || + list->push_back(path, thd->mem_root))) + MYSQL_YYABORT; + Item *extract= new (thd->mem_root) Item_func_json_extract(thd, *list); + if (unlikely(extract == NULL)) + MYSQL_YYABORT; + $$= new (thd->mem_root) Item_func_json_unquote(thd, extract); + if (unlikely($$ == NULL)) + MYSQL_YYABORT; + } + ; + ; + string_factor_expr: primary_expr | string_factor_expr COLLATE_SYM collation_name