diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index cf23fc7641ef9..322329cdc1725 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -5263,4 +5263,199 @@ SET @obj1='{ "a": 1,"b": 2,"c": 3}'; SELECT JSON_OBJECT_FILTER_KEYS (@obj1,@arr1); JSON_OBJECT_FILTER_KEYS (@obj1,@arr1) NULL +# +# MDEV-37072: Implement IS JSON predicate +# +set names utf8mb4; +SELECT '[1, 2]' IS JSON; +'[1, 2]' IS JSON +1 +SELECT '{"key1":1, "key2":[2,3]}' IS JSON; +'{"key1":1, "key2":[2,3]}' IS JSON +1 +SELECT '123' IS JSON; +'123' IS JSON +1 +SELECT 'null' IS JSON; +'null' IS JSON +1 +SELECT 'invalid' IS JSON; +'invalid' IS JSON +0 +SELECT '{"key1":1, "key2":[2,3]' IS JSON; +'{"key1":1, "key2":[2,3]' IS JSON +0 +SELECT '[1, 2' IS JSON; +'[1, 2' IS JSON +0 +SELECT 'NULL' IS JSON; +'NULL' IS JSON +0 +SELECT 'invalid' IS NOT JSON; +'invalid' IS NOT JSON +1 +SELECT '{"key1":1, "key2":[2,3]' IS NOT JSON; +'{"key1":1, "key2":[2,3]' IS NOT JSON +1 +SELECT '[1, 2]' IS NOT JSON; +'[1, 2]' IS NOT JSON +0 +SELECT '{"key1":1, "key2":[2,3]}' IS NOT JSON; +'{"key1":1, "key2":[2,3]}' IS NOT JSON +0 +# Type constraints +SELECT js, +js IS JSON "json?", +js IS JSON VALUE "value?", +js IS JSON SCALAR "scalar?", +js IS JSON OBJECT "object?", +js IS JSON ARRAY "array?" +FROM (VALUES +('123'), ('"string"'), ('{"key":1}'), ('[1,2,3]'), ('[]'), ('{}'), +('true'), ('false'), ('null')) foo(js); +js json? value? scalar? object? array? +123 1 1 1 0 0 +"string" 1 1 1 0 0 +{"key":1} 1 1 0 1 0 +[1,2,3] 1 1 0 0 1 +[] 1 1 0 0 1 +{} 1 1 0 1 0 +true 1 1 1 0 0 +false 1 1 1 0 0 +null 1 1 1 0 0 +# UNIQUE KEYS constraint +SELECT js, +js IS JSON OBJECT WITH UNIQUE KEYS "object_with_unique", +js IS JSON OBJECT WITHOUT UNIQUE KEYS "object_without_unique", +js IS JSON ARRAY WITH UNIQUE KEYS "array_with_unique", +js IS JSON ARRAY WITHOUT UNIQUE KEYS "array_without_unique" +FROM (VALUES +('{"a":1, "b":2, "c":3}'), +('{"a":1, "a":2, "a":3}'), +('[{"a":"1"},{"c":"2","c":"3"}]'), +('[{"a":"1"},{"b":"2","c":"3"}]'), +('[1,2,3]'), ('[1,1,1]'), ('[]'), ('123'), +('"string"'), ('true'), ('null')) foo(js); +js object_with_unique object_without_unique array_with_unique array_without_unique +{"a":1, "b":2, "c":3} 1 1 0 0 +{"a":1, "a":2, "a":3} 0 1 0 0 +[{"a":"1"},{"c":"2","c":"3"}] 0 0 0 1 +[{"a":"1"},{"b":"2","c":"3"}] 0 0 1 1 +[1,2,3] 0 0 1 1 +[1,1,1] 0 0 1 1 +[] 0 0 1 1 +123 0 0 0 0 +"string" 0 0 0 0 +true 0 0 0 0 +null 0 0 0 0 +# Test with table data +CREATE TABLE test_json ( +json_col JSON, +text_col TEXT +); +INSERT INTO test_json VALUES +('{"name":"Alice", "age":25}', '{"name":"Alice", "age":25}'), +('[1,2,3]', '[1,2,3]'), +('42', '42'), +('"hello"', '"hello"'), +('true', 'true'), +('null', 'null'), +('"invalid"', 'invalid'), +('{"key1":1, "key2":2}', '{"key1":1, "key2":2}'), +('{"key1":1, "key1":2}', '{"key1":1, "key1":2}'); +SELECT +json_col, +json_col IS JSON as is_json, +json_col IS JSON VALUE as is_value, +json_col IS JSON SCALAR as is_scalar, +json_col IS JSON OBJECT as is_object, +json_col IS JSON ARRAY as is_array, +json_col IS JSON OBJECT WITH UNIQUE KEYS as object_with_unique, +json_col IS JSON OBJECT WITHOUT UNIQUE KEYS as object_without_unique +FROM test_json; +json_col is_json is_value is_scalar is_object is_array object_with_unique object_without_unique +{"name":"Alice", "age":25} 1 1 0 1 0 1 1 +[1,2,3] 1 1 0 0 1 0 0 +42 1 1 1 0 0 0 0 +"hello" 1 1 1 0 0 0 0 +true 1 1 1 0 0 0 0 +null 1 1 1 0 0 0 0 +"invalid" 1 1 1 0 0 0 0 +{"key1":1, "key2":2} 1 1 0 1 0 1 1 +{"key1":1, "key1":2} 1 1 0 1 0 0 1 +SELECT +text_col, +text_col IS JSON as is_json, +text_col IS JSON VALUE as is_value, +text_col IS JSON SCALAR as is_scalar, +text_col IS JSON OBJECT as is_object, +text_col IS JSON ARRAY as is_array +FROM test_json; +text_col is_json is_value is_scalar is_object is_array +{"name":"Alice", "age":25} 1 1 0 1 0 +[1,2,3] 1 1 0 0 1 +42 1 1 1 0 0 +"hello" 1 1 1 0 0 +true 1 1 1 0 0 +null 1 1 1 0 0 +invalid 0 0 0 0 0 +{"key1":1, "key2":2} 1 1 0 1 0 +{"key1":1, "key1":2} 1 1 0 1 0 +SELECT +COUNT(*) as total_rows, +SUM(json_col IS JSON) as valid_json_count, +SUM(json_col IS JSON OBJECT) as object_count, +SUM(json_col IS JSON ARRAY) as array_count, +SUM(json_col IS JSON SCALAR) as scalar_count +FROM test_json; +total_rows valid_json_count object_count array_count scalar_count +9 9 3 1 5 +# Edge cases +SELECT js, +js IS JSON "json?", +js IS JSON VALUE "value?", +js IS JSON SCALAR "scalar?", +js IS JSON OBJECT "object?", +js IS JSON ARRAY "array?" +FROM (VALUES +(NULL), (''), (' '), ('\n'), ('\t'), +('{"key":1,}'), ('{"key":1, "key2":}'), ('{key:1}'), +('{"emoji":"😊"}'), ('{"unicode":"\\u0041"}'), +(x'48656C6C6F'), ('2'), (CONCAT('{"a":', 1, '}')), +(JSON_OBJECT('a', 1, 'b', 2)), (JSON_ARRAY(1, 2, 3)), (JSON_QUOTE('hello'))) foo(js); +js json? value? scalar? object? array? +NULL NULL NULL NULL NULL NULL + 0 0 0 0 0 + 0 0 0 0 0 + + 0 0 0 0 0 + 0 0 0 0 0 +{"key":1,} 0 0 0 0 0 +{"key":1, "key2":} 0 0 0 0 0 +{key:1} 0 0 0 0 0 +{"emoji":"😊"} 1 1 0 1 0 +{"unicode":"\u0041"} 1 1 0 1 0 +Hello 0 0 0 0 0 +2 1 1 1 0 0 +{"a":1} 1 1 0 1 0 +{"a": 1, "b": 2} 1 1 0 1 0 +[1, 2, 3] 1 1 0 0 1 +"hello" 1 1 1 0 0 +# Large json object with unique keys +CREATE PROCEDURE build_large_json() +BEGIN +SET @json='{"key1":1'; +SET @i=2; +WHILE @i <= 1000 DO +SET @json = CONCAT(@json, ',"key', @i, '":', @i); +SET @i = @i + 1; +END WHILE; +SET @json = CONCAT(@json, '}'); +END| +CALL build_large_json()| +SELECT @json IS JSON OBJECT WITH UNIQUE KEYS AS unique_keys| +unique_keys +1 +DROP PROCEDURE build_large_json| +DROP TABLE test_json; # End of 11.2 Test diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index 6728dddcca51a..de9bef4c5ad0c 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -4162,4 +4162,137 @@ SET CHARACTER SET utf8; SET @obj1='{ "a": 1,"b": 2,"c": 3}'; SELECT JSON_OBJECT_FILTER_KEYS (@obj1,@arr1); + +--echo # +--echo # MDEV-37072: Implement IS JSON predicate +--echo # + +set names utf8mb4; + +--disable_view_protocol +SELECT '[1, 2]' IS JSON; +SELECT '{"key1":1, "key2":[2,3]}' IS JSON; +SELECT '123' IS JSON; +SELECT 'null' IS JSON; + +SELECT 'invalid' IS JSON; +SELECT '{"key1":1, "key2":[2,3]' IS JSON; +SELECT '[1, 2' IS JSON; +SELECT 'NULL' IS JSON; + +SELECT 'invalid' IS NOT JSON; +SELECT '{"key1":1, "key2":[2,3]' IS NOT JSON; +SELECT '[1, 2]' IS NOT JSON; +SELECT '{"key1":1, "key2":[2,3]}' IS NOT JSON; + +--echo # Type constraints + +SELECT js, + js IS JSON "json?", + js IS JSON VALUE "value?", + js IS JSON SCALAR "scalar?", + js IS JSON OBJECT "object?", + js IS JSON ARRAY "array?" +FROM (VALUES + ('123'), ('"string"'), ('{"key":1}'), ('[1,2,3]'), ('[]'), ('{}'), + ('true'), ('false'), ('null')) foo(js); + +--echo # UNIQUE KEYS constraint + +SELECT js, + js IS JSON OBJECT WITH UNIQUE KEYS "object_with_unique", + js IS JSON OBJECT WITHOUT UNIQUE KEYS "object_without_unique", + js IS JSON ARRAY WITH UNIQUE KEYS "array_with_unique", + js IS JSON ARRAY WITHOUT UNIQUE KEYS "array_without_unique" +FROM (VALUES + ('{"a":1, "b":2, "c":3}'), + ('{"a":1, "a":2, "a":3}'), + ('[{"a":"1"},{"c":"2","c":"3"}]'), + ('[{"a":"1"},{"b":"2","c":"3"}]'), + ('[1,2,3]'), ('[1,1,1]'), ('[]'), ('123'), + ('"string"'), ('true'), ('null')) foo(js); + +--echo # Test with table data + +CREATE TABLE test_json ( + json_col JSON, + text_col TEXT +); + +INSERT INTO test_json VALUES +('{"name":"Alice", "age":25}', '{"name":"Alice", "age":25}'), +('[1,2,3]', '[1,2,3]'), +('42', '42'), +('"hello"', '"hello"'), +('true', 'true'), +('null', 'null'), +('"invalid"', 'invalid'), +('{"key1":1, "key2":2}', '{"key1":1, "key2":2}'), +('{"key1":1, "key1":2}', '{"key1":1, "key1":2}'); + +SELECT + json_col, + json_col IS JSON as is_json, + json_col IS JSON VALUE as is_value, + json_col IS JSON SCALAR as is_scalar, + json_col IS JSON OBJECT as is_object, + json_col IS JSON ARRAY as is_array, + json_col IS JSON OBJECT WITH UNIQUE KEYS as object_with_unique, + json_col IS JSON OBJECT WITHOUT UNIQUE KEYS as object_without_unique +FROM test_json; + +SELECT + text_col, + text_col IS JSON as is_json, + text_col IS JSON VALUE as is_value, + text_col IS JSON SCALAR as is_scalar, + text_col IS JSON OBJECT as is_object, + text_col IS JSON ARRAY as is_array +FROM test_json; + +SELECT + COUNT(*) as total_rows, + SUM(json_col IS JSON) as valid_json_count, + SUM(json_col IS JSON OBJECT) as object_count, + SUM(json_col IS JSON ARRAY) as array_count, + SUM(json_col IS JSON SCALAR) as scalar_count +FROM test_json; + +--echo # Edge cases + +SELECT js, + js IS JSON "json?", + js IS JSON VALUE "value?", + js IS JSON SCALAR "scalar?", + js IS JSON OBJECT "object?", + js IS JSON ARRAY "array?" +FROM (VALUES + (NULL), (''), (' '), ('\n'), ('\t'), + ('{"key":1,}'), ('{"key":1, "key2":}'), ('{key:1}'), + ('{"emoji":"😊"}'), ('{"unicode":"\\u0041"}'), + (x'48656C6C6F'), ('2'), (CONCAT('{"a":', 1, '}')), + (JSON_OBJECT('a', 1, 'b', 2)), (JSON_ARRAY(1, 2, 3)), (JSON_QUOTE('hello'))) foo(js); +--enable_view_protocol + +--echo # Large json object with unique keys + +DELIMITER |; +CREATE PROCEDURE build_large_json() +BEGIN + SET @json='{"key1":1'; + SET @i=2; + WHILE @i <= 1000 DO + SET @json = CONCAT(@json, ',"key', @i, '":', @i); + SET @i = @i + 1; + END WHILE; + SET @json = CONCAT(@json, '}'); +END| +CALL build_large_json()| +SELECT @json IS JSON OBJECT WITH UNIQUE KEYS AS unique_keys| +DROP PROCEDURE build_large_json| +DELIMITER ;| + +DROP TABLE test_json; + + --echo # End of 11.2 Test diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 1c86ee059fa93..3e7a21794045d 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -6262,3 +6262,142 @@ bool Item_func_json_object_to_array::fix_length_and_dec(THD *thd) return FALSE; } + + +static bool check_unique_keys(json_engine_t *je) +{ + HASH unique_keys; + bool result= true; + + if (my_hash_init(PSI_INSTRUMENT_ME, &unique_keys, je->s.cs, + 0, 0, 0, get_key_name, my_free, 0)) + return false; + + while (json_scan_next(je) == 0) + { + if (je->state == JST_KEY) + { + const uchar *key_start, *key_end; + int key_len; + + key_start= je->s.c_str; + do + { + key_end= je->s.c_str; + } while (json_read_keyname_chr(je) == 0); + + if (unlikely(je->s.error)) + { + my_hash_free(&unique_keys); + return false; + } + + key_len= (int)(key_end - key_start); + + char *curr_key= (char*)my_malloc(PSI_NOT_INSTRUMENTED, key_len + 1, MYF(0)); + if (!curr_key) + { + my_hash_free(&unique_keys); + return false; + } + + memcpy(curr_key, key_start, key_len); + curr_key[key_len]= '\0'; + + if (my_hash_search(&unique_keys, (const uchar*)curr_key, key_len)) + { + my_free(curr_key); + my_hash_free(&unique_keys); + return false; + } + + if (my_hash_insert(&unique_keys, (const uchar*)curr_key)) + { + my_free(curr_key); + my_hash_free(&unique_keys); + return false; + } + + // Skip the value + if (json_read_value(je)) + { + my_hash_free(&unique_keys); + return false; + } + if (!json_value_scalar(je)) + { + if (json_skip_level(je)) + { + my_hash_free(&unique_keys); + return false; + } + } + } + } + my_hash_free(&unique_keys); + return result; +} + + +bool Item_func_is_json::val_bool() +{ + bool result= true; + bool unique_keys= true; + String *js= args[0]->val_json(&tmp_value); + + if ((null_value= args[0]->null_value)) + return 0; + + json_scan_start(&je, js->charset(), (const uchar*)js->ptr(), + (const uchar*)js->end()); + + if (json_read_value(&je)) + return negated; + + switch (type_constraint) + { + case JSON_VALUE_ANY: + result= true; + break; + case JSON_ARRAY: + result= (je.value_type == JSON_VALUE_ARRAY); + break; + case JSON_OBJECT: + result= (je.value_type == JSON_VALUE_OBJECT); + break; + case JSON_SCALAR: + result= (json_value_scalar(&je)); + break; + } + + while (json_scan_next(&je) == 0) {} + if (je.s.error) + return negated; + + if (with_unique_keys) + { + if (je.value_type == JSON_VALUE_OBJECT || je.value_type == JSON_VALUE_ARRAY) + { + // Reset the json engine to the beginning of the json object + json_scan_start(&je, js->charset(), (const uchar*)js->ptr(), + (const uchar*)js->end()); + unique_keys= check_unique_keys(&je); + } + } + + return negated ? !(result && unique_keys) : (result && unique_keys); +} + + +bool Item_func_is_json::fix_length_and_dec(THD *thd) +{ + mem_root_dynamic_array_init(thd->mem_root, PSI_INSTRUMENT_MEM, + &je.stack, sizeof(int), NULL, + JSON_DEPTH_DEFAULT, JSON_DEPTH_INC, MYF(0)); + + if (Item_bool_func::fix_length_and_dec(thd)) + return TRUE; + set_maybe_null(); + + return FALSE; +} \ No newline at end of file diff --git a/sql/item_jsonfunc.h b/sql/item_jsonfunc.h index 35dbf01885fe4..b59e1ed583efa 100644 --- a/sql/item_jsonfunc.h +++ b/sql/item_jsonfunc.h @@ -1031,4 +1031,39 @@ class Item_func_json_object_filter_keys: public Item_str_func }; +class Item_func_is_json: public Item_bool_func +{ +public: + enum constraint_t { JSON_VALUE_ANY, JSON_ARRAY, JSON_OBJECT, JSON_SCALAR }; + +protected: + String tmp_value; + constraint_t type_constraint; + bool negated; + bool with_unique_keys; + json_engine_t je; + +public: + Item_func_is_json(THD *thd, Item *json, bool is_not, + ulong type_constraint, + bool with_unique_keys) + : Item_bool_func(thd, json), + type_constraint(static_cast(type_constraint)), + negated(is_not), + with_unique_keys(with_unique_keys) + {} + + bool val_bool() override; + bool fix_length_and_dec(THD *thd) override; + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("is_json") }; + return name; + } + + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + #endif /* ITEM_JSONFUNC_INCLUDED */ diff --git a/sql/lex.h b/sql/lex.h index 41a34ef738989..a43b3b639a426 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -70,6 +70,7 @@ SYMBOL symbols[] = { { "ANALYZE", SYM(ANALYZE_SYM)}, { "AND", SYM(AND_SYM)}, { "ANY", SYM(ANY_SYM)}, + { "ARRAY", SYM(ARRAY_SYM)}, { "AS", SYM(AS)}, { "ASC", SYM(ASC)}, { "ASCII", SYM(ASCII_SYM)}, @@ -442,6 +443,7 @@ SYMBOL symbols[] = { { "NUMBER", SYM(NUMBER_MARIADB_SYM)}, { "NUMERIC", SYM(NUMERIC_SYM)}, { "NVARCHAR", SYM(NVARCHAR_SYM)}, + { "OBJECT", SYM(OBJECT_SYM)}, { "OF", SYM(OF_SYM)}, { "OFFSET", SYM(OFFSET_SYM)}, { "OLD_PASSWORD", SYM(OLD_PASSWORD_SYM)}, @@ -566,6 +568,7 @@ SYMBOL symbols[] = { { "ROW_NUMBER", SYM(ROW_NUMBER_SYM)}, { "RTREE", SYM(RTREE_SYM)}, { "SAVEPOINT", SYM(SAVEPOINT_SYM)}, + { "SCALAR", SYM(SCALAR_SYM)}, { "SCHEDULE", SYM(SCHEDULE_SYM)}, { "SCHEMA", SYM(DATABASE)}, { "SCHEMA_NAME", SYM(SCHEMA_NAME_SYM)}, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index e77b17a4e091b..4959c8bd5f6e2 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -242,6 +242,10 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() Lex_select_lock select_lock; Lex_select_limit select_limit; Lex_order_limit_lock *order_limit_lock; + struct { + bool with_unique_keys; + ulong type_constraint; + } json_predicate; /* pointers */ Lex_ident_sys *ident_sys_ptr; @@ -362,9 +366,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); */ %ifdef MARIADB -%expect 63 +%expect 69 %else -%expect 64 +%expect 70 %endif /* @@ -767,6 +771,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token ALGORITHM_SYM %token ALWAYS_SYM %token ANY_SYM /* SQL-2003-R */ +%token ARRAY_SYM %token ASCII_SYM /* MYSQL-FUNC */ %token AT_SYM /* SQL-2003-R */ %token ATOMIC_SYM /* SQL-2003-R */ @@ -998,6 +1003,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token NUMBER_MARIADB_SYM /* SQL-2003-N */ %token NUMBER_ORACLE_SYM /* Oracle-R, PLSQL-R */ %token NVARCHAR_SYM +%token OBJECT_SYM %token OF_SYM /* SQL-1992-R, Oracle-R */ %token OFFSET_SYM %token OLD_PASSWORD_SYM @@ -1073,6 +1079,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token ROW_FORMAT_SYM %token RTREE_SYM %token SAVEPOINT_SYM /* SQL-2003-R */ +%token SCALAR_SYM %token SCHEDULE_SYM %token SCHEMA_NAME_SYM /* SQL-2003-N */ %token SECOND_SYM /* SQL-2003-R */ @@ -1437,6 +1444,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type json_on_response +%type json_type_constraint +%type json_key_unique_constraint +%type json_predicate + %type field_type field_type_all field_type_all_builtin field_type_all_with_typedefs qualified_field_type @@ -9844,6 +9855,24 @@ boolean_test: if (unlikely($$ == NULL)) MYSQL_YYABORT; } + | boolean_test IS json_predicate %prec IS + { + $$= new (thd->mem_root) Item_func_is_json(thd, $1, + false, + $3.type_constraint, + $3.with_unique_keys); + if (unlikely($$ == NULL)) + MYSQL_YYABORT; + } + | boolean_test IS not json_predicate %prec IS + { + $$= new (thd->mem_root) Item_func_is_json(thd, $1, + true, + $4.type_constraint, + $4.with_unique_keys); + if (unlikely($$ == NULL)) + MYSQL_YYABORT; + } | boolean_test EQUAL_SYM predicate %prec EQUAL_SYM { $$= new (thd->mem_root) Item_func_equal(thd, $1, $3); @@ -9865,6 +9894,29 @@ boolean_test: | predicate %prec BETWEEN_SYM ; +json_predicate: + JSON_SYM json_type_constraint json_key_unique_constraint + { + $$.type_constraint= $2; + $$.with_unique_keys= $3; + } + ; + +json_type_constraint: + ARRAY_SYM { $$= Item_func_is_json::JSON_ARRAY; } + | OBJECT_SYM { $$= Item_func_is_json::JSON_OBJECT; } + | SCALAR_SYM { $$= Item_func_is_json::JSON_SCALAR; } + | VALUE_SYM { $$= Item_func_is_json::JSON_VALUE_ANY; } + | /* empty */ { $$= Item_func_is_json::JSON_VALUE_ANY; } + ; + +json_key_unique_constraint: + WITH UNIQUE_SYM { $$ = true; } + | WITH UNIQUE_SYM KEYS { $$ = true; } + | WITHOUT UNIQUE_SYM { $$ = false; } + | WITHOUT UNIQUE_SYM KEYS { $$ = false; } + | /* empty */ { $$ = false; } + predicate: predicate IN_SYM subquery { @@ -16609,6 +16661,7 @@ keyword_func_sp_var_and_label: | AGGREGATE_SYM | ALGORITHM_SYM | ALWAYS_SYM + | ARRAY_SYM | AT_SYM | ATOMIC_SYM | AUTHORS_SYM @@ -16780,6 +16833,7 @@ keyword_func_sp_var_and_label: | NODEGROUP_SYM | NONE_SYM | NOTFOUND_SYM + | OBJECT_SYM | OF_SYM | OLD_PASSWORD_SYM | ONE_SYM @@ -16838,6 +16892,7 @@ keyword_func_sp_var_and_label: | ROWTYPE_MARIADB_SYM | ROW_FORMAT_SYM | RTREE_SYM + | SCALAR_SYM | SCHEDULE_SYM | SCHEMA_NAME_SYM | SEQUENCE_SYM