diff --git a/mysql-test/suite/json/r/json_table_mysql.result b/mysql-test/suite/json/r/json_table_mysql.result index 4a3c697660fc6..80bef96596ad5 100644 --- a/mysql-test/suite/json/r/json_table_mysql.result +++ b/mysql-test/suite/json/r/json_table_mysql.result @@ -106,6 +106,86 @@ id jpath json_path jexst 3 NULL NULL 1 select * from json_table( +'[{"name":"Jeans","sizes":[32,34,36],"details":{"fit":"regular"},"flag":true,"jsnull":null}, + {"name":"T-Shirt","sizes":["Medium","Large"],"details":{"fit":"loose"},"flag":false,"jsnull":null}, + {"name":"Cellphone"}]', +'$[*]' columns (name varchar(10) path '$.name', +sizes varchar(100) format json path '$.sizes', +details text format json path '$.details', +flag varchar(10) format json path '$.flag', +jsnull varchar(10) format json path '$.jsnull') +) as jt; +name sizes details flag jsnull +Jeans [32,34,36] {"fit":"regular"} true null +T-Shirt ["Medium","Large"] {"fit":"loose"} false null +Cellphone NULL NULL NULL NULL +select * from +json_table( +'[{"doc":[1,2]},{"doc":{"k":"v"}},{"doc":null},{}]', +'$[*]' columns (doc json format json path '$.doc' null on empty) +) as jt; +doc +[1,2] +{"k":"v"} +null +NULL +SELECT HEX(doc) FROM +json_table( +'[{"doc":[1,2]},{}]', +'$[*]' columns (doc blob format json path '$.doc' null on empty) +) as jt; +HEX(doc) +5B312C325D +NULL +select * from +json_table( +'[{"name":"Jeans","sizes":[32,34,36]},{"name":"Cellphone"}]', +'$[*]' columns (name varchar(10) path '$.name', +sizes varchar(100) format json path '$.sizes' + default '[99]' on empty) +) as jt; +name sizes +Jeans [32,34,36] +Cellphone [99] +select * from +json_table( +'[{"a":[1]},{"a":[2]}]', +'$' columns (a varchar(20) format json path '$[*].a' error on error) +) as jt; +ERROR HY000: Can't store multiple matches of the path in the column 'a' of JSON_TABLE 'jt'. +select * from +json_table( +'[{"a":[1,2,3]}]', +'$[*]' columns (a int format json path '$.a') +) as jt; +ERROR HY000: Incorrect usage of FORMAT JSON and non-string JSON_TABLE column +create view v_fmt as +select * from +json_table( +'[{"sizes":[1,2]}]', +'$[*]' columns (sizes varchar(20) format json path '$.sizes') +) as jt; +select * from v_fmt; +sizes +[1,2] +show create view v_fmt; +View Create View character_set_client collation_connection +v_fmt CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v_fmt` AS select `jt`.`sizes` AS `sizes` from JSON_TABLE('[{"sizes":[1,2]}]', '$[*]' COLUMNS (`sizes` varchar(20) FORMAT JSON PATH '$.sizes')) `jt` latin1 latin1_swedish_ci +drop view v_fmt; +create view v_fmt_cs as +select * from +json_table( +'[{"sizes":[1,2]}]', +'$[*]' columns ( +sizes varchar(20) character set utf8mb4 collate utf8mb4_bin +format json path '$.sizes') +) as jt; +show create view v_fmt_cs; +View Create View character_set_client collation_connection +v_fmt_cs CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v_fmt_cs` AS select `jt`.`sizes` AS `sizes` from JSON_TABLE('[{"sizes":[1,2]}]', '$[*]' COLUMNS (`sizes` varchar(20) CHARSET utf8mb4 COLLATE utf8mb4_bin FORMAT JSON PATH '$.sizes')) `jt` latin1 latin1_swedish_ci +drop view v_fmt_cs; +select * from +json_table( '[{"a":"3"},{"a":2},{"b":1},{"a":0}]', '$[*]' columns (id for ordinality, jpath varchar(100) path '$.a' error on empty, diff --git a/mysql-test/suite/json/t/json_table_mysql.test b/mysql-test/suite/json/t/json_table_mysql.test index 6ef834f3e6e92..93881703b450f 100644 --- a/mysql-test/suite/json/t/json_table_mysql.test +++ b/mysql-test/suite/json/t/json_table_mysql.test @@ -84,6 +84,73 @@ select * from #eval $query; #eval explain $query; +select * from + json_table( + '[{"name":"Jeans","sizes":[32,34,36],"details":{"fit":"regular"},"flag":true,"jsnull":null}, + {"name":"T-Shirt","sizes":["Medium","Large"],"details":{"fit":"loose"},"flag":false,"jsnull":null}, + {"name":"Cellphone"}]', + '$[*]' columns (name varchar(10) path '$.name', + sizes varchar(100) format json path '$.sizes', + details text format json path '$.details', + flag varchar(10) format json path '$.flag', + jsnull varchar(10) format json path '$.jsnull') + ) as jt; + +select * from + json_table( + '[{"doc":[1,2]},{"doc":{"k":"v"}},{"doc":null},{}]', + '$[*]' columns (doc json format json path '$.doc' null on empty) + ) as jt; + +SELECT HEX(doc) FROM + json_table( + '[{"doc":[1,2]},{}]', + '$[*]' columns (doc blob format json path '$.doc' null on empty) + ) as jt; + +select * from + json_table( + '[{"name":"Jeans","sizes":[32,34,36]},{"name":"Cellphone"}]', + '$[*]' columns (name varchar(10) path '$.name', + sizes varchar(100) format json path '$.sizes' + default '[99]' on empty) + ) as jt; + +--error ER_JSON_TABLE_MULTIPLE_MATCHES +select * from + json_table( + '[{"a":[1]},{"a":[2]}]', + '$' columns (a varchar(20) format json path '$[*].a' error on error) + ) as jt; + +--error ER_WRONG_USAGE +select * from + json_table( + '[{"a":[1,2,3]}]', + '$[*]' columns (a int format json path '$.a') + ) as jt; + +create view v_fmt as +select * from + json_table( + '[{"sizes":[1,2]}]', + '$[*]' columns (sizes varchar(20) format json path '$.sizes') + ) as jt; +select * from v_fmt; +show create view v_fmt; +drop view v_fmt; + +create view v_fmt_cs as +select * from + json_table( + '[{"sizes":[1,2]}]', + '$[*]' columns ( + sizes varchar(20) character set utf8mb4 collate utf8mb4_bin + format json path '$.sizes') + ) as jt; +show create view v_fmt_cs; +drop view v_fmt_cs; + --error ER_JSON_TABLE_ERROR_ON_FIELD select * from json_table( diff --git a/sql/json_table.cc b/sql/json_table.cc index ffcc99096c6dd..41aa8c1ef62d3 100644 --- a/sql/json_table.cc +++ b/sql/json_table.cc @@ -418,7 +418,7 @@ static void store_json_in_field(Field *f, const json_engine_t *je) } -static int store_json_in_json(Field *f, json_engine_t *je) +static int store_json_in_field_as_json(Field *f, json_engine_t *je) { const uchar *from= je->value_begin; const uchar *to; @@ -617,7 +617,7 @@ int ha_json_table::fill_column_values(THD *thd, uchar * buf, uchar *pos) { if (jc->m_format_json) { - if (!(error= store_json_in_json(*f, &je))) + if (!(error= store_json_in_field_as_json(*f, &je))) error= er_handler.errors; } else if (!(error= !json_value_scalar(&je))) @@ -931,6 +931,21 @@ TABLE *create_table_for_function(THD *thd, TABLE_LIST *sql_table) } +bool Json_table_column::enable_format_json() +{ + if (!m_field->type_handler()->is_general_purpose_string_type()) + { + my_error(ER_WRONG_USAGE, MYF(0), "FORMAT JSON", + "non-string JSON_TABLE column"); + return true; + } + + m_format_json= true; + m_explicit_format_json= true; + return false; +} + + int Json_table_column::set(THD *thd, enum_type ctype, const LEX_CSTRING &path, CHARSET_INFO *cs) { @@ -955,8 +970,11 @@ int Json_table_column::set(THD *thd, enum_type ctype, const LEX_CSTRING &path, */ m_path.s.c_str= (const uchar *) path.str; - if (ctype == PATH) - m_format_json= m_field->type_handler() == &type_handler_long_blob_json; + if (ctype == PATH && + Type_handler_json_common::is_json_type_handler(m_field->type_handler())) + { + m_format_json= true; + } return 0; } @@ -1018,10 +1036,13 @@ int Json_table_column::print(THD *thd, Field **f, String *str) { static const LEX_CSTRING path= { STRING_WITH_LEN(" PATH ") }; static const LEX_CSTRING exists_path= { STRING_WITH_LEN(" EXISTS PATH ") }; + bool is_json_type= + Type_handler_json_common::is_json_type_handler(m_field->type_handler()); (*f)->sql_type(column_type); - if ((m_format_json ? str->append(STRING_WITH_LEN(" JSON ")) : str->append(column_type))) + if (is_json_type ? str->append(STRING_WITH_LEN("JSON")) : + str->append(column_type)) return 1; if (((*f)->has_charset() && m_explicit_cs && (str->append(STRING_WITH_LEN(" CHARSET ")) || @@ -1029,6 +1050,8 @@ int Json_table_column::print(THD *thd, Field **f, String *str) (Charset(m_explicit_cs).can_have_collate_clause() && (str->append(STRING_WITH_LEN(" COLLATE ")) || str->append(&m_explicit_cs->coll_name))))) || + (m_explicit_format_json && + str->append(STRING_WITH_LEN(" FORMAT JSON"))) || str->append(m_column_type == PATH ? &path : &exists_path) || print_path(str, &m_path)) return 1; diff --git a/sql/json_table.h b/sql/json_table.h index bf20660200ead..9c15b15ec9bb9 100644 --- a/sql/json_table.h +++ b/sql/json_table.h @@ -151,6 +151,7 @@ class Json_table_column : public Sql_alloc enum_type m_column_type; bool m_format_json; + bool m_explicit_format_json; json_path_t m_path; On_response m_on_error; On_response m_on_empty; @@ -165,12 +166,14 @@ class Json_table_column : public Sql_alloc int set(THD *thd, enum_type ctype, const LEX_CSTRING &path, CHARSET_INFO *cs); int set(THD *thd, enum_type ctype, const LEX_CSTRING &path, const Lex_column_charset_collation_attrs_st &cl); + bool enable_format_json(); Json_table_column(Create_field *f, Json_table_nested_path *nest) : m_field(f), m_nest(nest), m_explicit_cs(NULL) { m_on_error.m_response= RESPONSE_NOT_SPECIFIED; m_on_empty.m_response= RESPONSE_NOT_SPECIFIED; m_format_json= false; + m_explicit_format_json= false; } int print(THD *tnd, Field **f, String *str); }; @@ -291,4 +294,3 @@ table_map add_table_function_dependencies(List *join_list, table_map nest_tables, bool *error); #endif /* JSON_TABLE_INCLUDED */ - diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 21525c7990ccf..43d32e9944ace 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -12310,6 +12310,20 @@ json_table_column_type: MYSQL_YYABORT; } } + | json_table_field_type FORMAT_SYM JSON_SYM PATH_SYM json_text_literal + json_opt_on_empty_or_error + { + if (Lex->last_field->set_attributes(thd, $1, + COLUMN_DEFINITION_TABLE_FIELD)) + MYSQL_YYABORT; + if (Lex->json_table->m_cur_json_table_column->enable_format_json() || + Lex->json_table->m_cur_json_table_column-> + set(thd, Json_table_column::PATH, $5, + $1.charset_collation_attrs())) + { + MYSQL_YYABORT; + } + } | json_table_field_type EXISTS PATH_SYM json_text_literal { if (Lex->last_field->set_attributes(thd, $1,