diff --git a/mysql-test/mysql-source-code-meta.patch b/mysql-test/mysql-source-code-meta.patch index 5794762fda619da21285b748c952df7832d5803f..b0c1936fbb3ed36ca2f53c9052ab4d9a20615cd9 100644 --- a/mysql-test/mysql-source-code-meta.patch +++ b/mysql-test/mysql-source-code-meta.patch @@ -1120,6 +1120,56 @@ index 0530ca15..efeaf90e 100644 enum_binlog_command binlog_command, const char *query, size_t query_length, const char *db, const char *table_name); +diff --git a/sql/item_json_func.cc b/sql/item_json_func.cc +index 31aade1c..8a1012e4 100644 +--- a/sql/item_json_func.cc ++++ b/sql/item_json_func.cc +@@ -3656,7 +3656,7 @@ bool Item_func_array_cast::fix_fields(THD *thd, Item **ref) { + @param item the Item in which the cast operation is performed + @param[out] str the string to print to + */ +-static void print_cast_type(Cast_target cast_type, const Item *item, ++void print_cast_type(Cast_target cast_type, const Item *item, + String *str) { + const unsigned decimals = item->decimals; + switch (cast_type) { +diff --git a/sql/item_json_func.h b/sql/item_json_func.h +index 791e9b3e..7bc26c04 100644 +--- a/sql/item_json_func.h ++++ b/sql/item_json_func.h +@@ -1103,6 +1103,9 @@ class Item_func_json_value final : public Item_func { + my_decimal *val_decimal(my_decimal *value) override; + bool get_date(MYSQL_TIME *ltime, my_time_flags_t flags) override; + bool get_time(MYSQL_TIME *ltime) override; ++ Json_on_response_type m_on_empty; ++ Json_on_response_type m_on_error; ++ Cast_target m_cast_target; + + private: + /// Represents a default value given in JSON_VALUE's DEFAULT xxx ON EMPTY or +@@ -1112,15 +1115,12 @@ class Item_func_json_value final : public Item_func { + /// Parsed path. + Json_path m_path_json; + /// Type of the ON EMPTY clause. +- Json_on_response_type m_on_empty; + /// Type of the ON ERROR clause. +- Json_on_response_type m_on_error; + /// The default value for ON EMPTY (if not ERROR or NULL ON EMPTY). + unique_ptr_destroy_only m_default_empty; + /// The default value for ON EMPTY (if not ERROR or NULL ON EMPTY). + unique_ptr_destroy_only m_default_error; + /// The target data type. +- Cast_target m_cast_target; + + /** + Creates a Json_value_default object representing the default value given in +@@ -1244,4 +1244,6 @@ bool sort_and_remove_dups(const Json_wrapper &orig, Sorted_index_array *v); + + bool save_json_to_field(THD *thd, Field *field, const Json_wrapper *w, + bool no_error); ++void print_cast_type(Cast_target cast_type, const Item *item, ++ String *str); + #endif /* ITEM_JSON_FUNC_INCLUDED */ diff --git a/sql/locking_service.cc b/sql/locking_service.cc index 9ca8e21a..ef215f3d 100644 --- a/sql/locking_service.cc @@ -3892,6 +3942,6 @@ index 19110c7a..131a4c69 100644 ELSE() # This is a fall-back. FILE(WRITE ${INFO_SRC} "\nMySQL source ${VERSION}\n") -+ FILE(APPEND ${INFO_SRC} "\nCantian patch source 1.0.1\n") ++ FILE(APPEND ${INFO_SRC} "\nCantian patch source 1.0.2\n") ENDIF() ENDMACRO(CREATE_INFO_SRC) \ No newline at end of file diff --git a/mysql-test/suite/ctc/r/ctc_ddl_func_index.result b/mysql-test/suite/ctc/r/ctc_ddl_func_index.result index d8e34ff627166fa30b46637369164ec6b8ad43fb..c61b0af2c80c04efe02f38c5c31042f14b6f5918 100644 --- a/mysql-test/suite/ctc/r/ctc_ddl_func_index.result +++ b/mysql-test/suite/ctc/r/ctc_ddl_func_index.result @@ -49,8 +49,48 @@ id select_type table partitions type possible_keys key key_len ref rows filtered 1 SIMPLE t1 NULL ref func_index_2 func_index_2 7 const 4 100.00 NULL Warnings: Note 1003 /* select#1 */ select `db1`.`t1`.`c1` AS `c1`,`db1`.`t1`.`c2` AS `c2`,`db1`.`t1`.`c3` AS `c3`,`db1`.`t1`.`c4` AS `c4` from `db1`.`t1` where (substr(`c4`,1,1) = 'a') +create index func_index_3 on t1 ((substr(upper(c4), 1, 1))); +select * from t1 where substr(upper(c4), 1, 1) = 'a'; +c1 c2 c3 c4 +1 1 aaa aaa +2 2 aaA aBB +4 4 aaBa Aaa +explain select * from t1 where substr(upper(c4), 1, 1) = 'a'; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref func_index_3 func_index_3 7 const 1 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `db1`.`t1`.`c1` AS `c1`,`db1`.`t1`.`c2` AS `c2`,`db1`.`t1`.`c3` AS `c3`,`db1`.`t1`.`c4` AS `c4` from `db1`.`t1` where (substr(upper(`c4`),1,1) = 'a') +analyze table t1; +Table Op Msg_type Msg_text +db1.t1 analyze status OK +explain select * from t1 where substr(upper(c4), 1, 1) = 'a'; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref func_index_3 func_index_3 7 const 4 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `db1`.`t1`.`c1` AS `c1`,`db1`.`t1`.`c2` AS `c2`,`db1`.`t1`.`c3` AS `c3`,`db1`.`t1`.`c4` AS `c4` from `db1`.`t1` where (substr(upper(`c4`),1,1) = 'a') +create index func_index_4 on t1 ((upper(substr(c4, 1, 1)))); +select * from t1 where upper(substr(c4, 1, 1)) = 'a'; +c1 c2 c3 c4 +1 1 aaa aaa +2 2 aaA aBB +4 4 aaBa Aaa +explain select * from t1 where upper(substr(c4, 1, 1)) = 'a'; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref func_index_4 func_index_4 7 const 1 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `db1`.`t1`.`c1` AS `c1`,`db1`.`t1`.`c2` AS `c2`,`db1`.`t1`.`c3` AS `c3`,`db1`.`t1`.`c4` AS `c4` from `db1`.`t1` where (upper(substr(`c4`,1,1)) = 'a') +analyze table t1; +Table Op Msg_type Msg_text +db1.t1 analyze status OK +explain select * from t1 where upper(substr(c4, 1, 1)) = 'a'; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref func_index_4 func_index_4 7 const 4 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `db1`.`t1`.`c1` AS `c1`,`db1`.`t1`.`c2` AS `c2`,`db1`.`t1`.`c3` AS `c3`,`db1`.`t1`.`c4` AS `c4` from `db1`.`t1` where (upper(substr(`c4`,1,1)) = 'a') alter table t1 drop index func_index_1; drop index func_index_2 on t1; +drop index func_index_3 on t1; +drop index func_index_4 on t1; create table t2 (c1 int, c2 int, c3 varchar(10)); insert into t2 values (1, 1, 'aaa'), (2, 2, 'aaA'), (3, 3, 'AAA'), (4, 4, 'aaBa'); select * from t2; diff --git a/mysql-test/suite/ctc/t/ctc_ddl_func_index.test b/mysql-test/suite/ctc/t/ctc_ddl_func_index.test index 879c0c2cafaf0feeda52760154cc8d55b15faee2..df44be2aa111d7fd286c9df930059372621ee829 100644 --- a/mysql-test/suite/ctc/t/ctc_ddl_func_index.test +++ b/mysql-test/suite/ctc/t/ctc_ddl_func_index.test @@ -20,10 +20,23 @@ explain select * from t1 where substr(c4, 1, 1) = 'a'; analyze table t1; explain select * from t1 where substr(c4, 1, 1) = 'a'; +# 创建嵌套函数索引场景 +create index func_index_3 on t1 ((substr(upper(c4), 1, 1))); +select * from t1 where substr(upper(c4), 1, 1) = 'a'; +explain select * from t1 where substr(upper(c4), 1, 1) = 'a'; +analyze table t1; +explain select * from t1 where substr(upper(c4), 1, 1) = 'a'; +create index func_index_4 on t1 ((upper(substr(c4, 1, 1)))); +select * from t1 where upper(substr(c4, 1, 1)) = 'a'; +explain select * from t1 where upper(substr(c4, 1, 1)) = 'a'; +analyze table t1; +explain select * from t1 where upper(substr(c4, 1, 1)) = 'a'; + # 删除函数索引 alter table t1 drop index func_index_1; drop index func_index_2 on t1; - +drop index func_index_3 on t1; +drop index func_index_4 on t1; # 创建函数索引异常场景 create table t2 (c1 int, c2 int, c3 varchar(10)); diff --git a/storage/ctc/ha_ctc_ddl.cc b/storage/ctc/ha_ctc_ddl.cc index 30205b905e7ac4afebcfba362d63c6cf7bac0723..f922c6f661dd33da28cb381944a7bedba960872d 100644 --- a/storage/ctc/ha_ctc_ddl.cc +++ b/storage/ctc/ha_ctc_ddl.cc @@ -48,6 +48,9 @@ #include "sql/sql_table.h" // primary_key_name #include "sql/sql_partition.h" #include "sql/item_func.h" +#include "sql/item_json_func.h" +#include "sql/table_function.h" + #include "my_time.h" #include "decimal.h" @@ -1247,7 +1250,7 @@ static int ctc_check_func_name(std::string func_name) const std::string mysql_func_name = mysql_func_name_to_ctc_map[i].mysql_func_name; if (func_name_len >= mysql_func_name.length() && func_name.compare(0, mysql_func_name.length(), mysql_func_name) == 0) { - my_printf_error(ER_DISALLOWED_OPERATION, "Function %s is not indexable", MYF(0), + my_printf_error(ER_DISALLOWED_OPERATION, "Function %s is not indexable", MYF(0), mysql_func_name_to_ctc_map[i].ctc_func_name.c_str()); return CT_ERROR; } @@ -1255,67 +1258,204 @@ static int ctc_check_func_name(std::string func_name) return CT_SUCCESS; } -static uint32_t ctc_fill_func_key_part(TABLE *form, THD *thd, TcDb__CtcDDLTableKeyPart *req_key_part, Value_generator *gcol_info) +static int recursively_get_dependency_item(TABLE *form, Item_func *item_func, TcDb__CtcDDLTableKeyPart *req_key_part, + uint32_t *col_item_count) { - Item_func *func_expr_item = dynamic_cast(gcol_info->expr_item); - if (func_expr_item == nullptr) { - my_printf_error(ER_DISALLOWED_OPERATION, "%s", MYF(0), - "[CTC_CREATE_TABLE]: CTC do not support this functional index."); - return CT_ERROR; - } + for (uint32_t i = 0; i < item_func->arg_count; i++) { + if (item_func->get_arg(i)->type() == Item::FUNC_ITEM) { + // nested function, would go recursively to do detection + int result = recursively_get_dependency_item(form, (Item_func *) item_func->get_arg(i), + req_key_part, col_item_count); + if (result != CT_SUCCESS) { + return result; + } + } - uint32_t arg_count = func_expr_item->arg_count; - if (arg_count == 0) { - my_printf_error(ER_DISALLOWED_OPERATION, "%s", MYF(0), - "[CTC_CREATE_TABLE]: There is no functional index."); - return CT_ERROR; - } + if (item_func->get_arg(i)->type() == Item::FIELD_ITEM) { + // the field* args[i] contains don't have proper m_field_index when it's a alter table scenario. + // thus we have to look up by name through metadata on the new TABLE* + Item_field *arg_item_field = (Item_field*) item_func->get_arg(i); + Field *field = ctc_get_field_by_name(form, arg_item_field->field->field_name); + if (field && field->is_gcol()) { + my_printf_error(ER_DISALLOWED_OPERATION, "%s", MYF(0), + "Cantian does not support index on generated column."); + return CT_ERROR; + } + + if (*col_item_count >= 1) { + my_printf_error(ER_DISALLOWED_OPERATION, "%s", MYF(0), + "Cantian does not support function indexes with multiple columns of arguments."); + return CT_ERROR; + } + req_key_part->name = const_cast(item_func->get_arg(i)->item_name.ptr()); + (*col_item_count)++; + } + } + return CT_SUCCESS; +} - if (ctc_check_func_name(std::string(func_expr_item->func_name())) != CT_SUCCESS) { - return CT_ERROR; - } - - req_key_part->is_func = true; - req_key_part->func_name = const_cast(func_expr_item->func_name()); - Item **args = func_expr_item->arguments(); - uint32_t col_item_count = 0; - Field *field = nullptr; - for (uint32_t i = 0; i < arg_count; i++) { - field = ctc_get_field_by_name(form, const_cast(args[i]->item_name.ptr())); - if (field && field->is_gcol()) { - my_printf_error(ER_DISALLOWED_OPERATION, "%s", MYF(0), - "Cantian does not support index on generated column."); - return CT_ERROR; +void ctc_rewrite_expression_clause(TABLE *form, const THD *thd, Item *item, String *out); + +void ctc_print_on_empty_or_error(TABLE *form, const THD *thd, String *out, bool on_empty, + Json_on_response_type response_type, Item *default_string) +{ + switch (response_type) { + case Json_on_response_type::ERROR: + out->append(STRING_WITH_LEN(" error")); + break; + case Json_on_response_type::NULL_VALUE: + out->append(STRING_WITH_LEN(" null")); + break; + case Json_on_response_type::DEFAULT: + out->append(STRING_WITH_LEN(" default ")); + ctc_rewrite_expression_clause(form, thd, default_string, out); + break; + case Json_on_response_type::IMPLICIT: + // Nothing to print when the clause was implicit. + return; + }; + + if (on_empty) { + out->append(STRING_WITH_LEN(" on empty")); + } else { + out->append(STRING_WITH_LEN(" on error")); } - if (args[i]->type() == Item::FIELD_ITEM) { - if (col_item_count >= 1) { +} + +#define JSON_VALUE_ON_EMPTY_ARG_IDX 2 +#define JSON_VALUE_ON_ERROR_ARG_IDX 3 + +// equivalent to Item_func_json_value::print, but stripped charset info +void ctc_item_print_json_value(TABLE *form, const THD *thd, Item* item, String *out) +{ + Item_func_json_value *item_func = (Item_func_json_value *) item; + out->append(STRING_WITH_LEN("json_value(")); + ctc_rewrite_expression_clause(form, thd, item_func->get_arg(0), out); + out->append(STRING_WITH_LEN(", ")); + ctc_rewrite_expression_clause(form, thd, item_func->get_arg(1), out); + out->append(STRING_WITH_LEN(" returning ")); + if (item_func->m_cast_target == ITEM_CAST_CHAR && item_func->collation.collation != &my_charset_bin) { + // don't add char, use CLOB instead + out->append(STRING_WITH_LEN("CLOB")); + } else { + print_cast_type(item_func->m_cast_target, item_func, out); + } + ctc_print_on_empty_or_error(form, thd, out, true, item_func->m_on_empty, + item_func->get_arg(JSON_VALUE_ON_EMPTY_ARG_IDX)); + ctc_print_on_empty_or_error(form, thd, out, false, item_func->m_on_error, + item_func->get_arg(JSON_VALUE_ON_ERROR_ARG_IDX)); + out->append(')'); +}; + +std::map item_func_printer = { + {"json_value", (ctc_item_print_t) ctc_item_print_json_value} +}; + +static void ctc_print_op(TABLE *form, const THD *thd, Item_func *item_func, String *out) +{ + out->append('('); + for (uint i = 0; i < item_func->arg_count - 1; i++) { + ctc_rewrite_expression_clause(form, thd, item_func->get_arg(i), out); + out->append(' '); + out->append(item_func->func_name()); + out->append(' '); + } + ctc_rewrite_expression_clause(form, thd, item_func->get_arg(item_func->arg_count - 1), out); + out->append(')'); +} + +static void ctc_print_func(TABLE *form, const THD *thd, Item_func *item_func, String *out) +{ + out->append(item_func->func_name()); + out->append('('); + for (uint i = 0; i < item_func->arg_count; i++) { + if (i != 0) out->append(','); + ctc_rewrite_expression_clause(form, thd, item_func->get_arg(i), out); + } + out->append(')'); +} + +// the origin implementation of Item_field::print would add extra back quote (`) when it's called, +// due to append_identifier() would explicitly use get_quote_char_for_identifier() to get quote char. +// meanwhile ctsql don't have support for that, ctc_print_field shall be used instead. + +static void ctc_print_field(TABLE *form MY_ATTRIBUTE((unused)), const THD *thd MY_ATTRIBUTE((unused)), + Item_ident *item_ident, String *out) +{ + // equivalent to append_identifier(thd, out, item_ident->field_name, strlen(item_ident->field_name), NULL, NULL) + // but without quote_char support + out->append(item_ident->field_name, strlen(item_ident->field_name), system_charset_info); +} + +// this expression rewrite process may only happen once during DDL, performance is not major concern +void ctc_rewrite_expression_clause(TABLE *form, const THD *thd, Item *item, String *out) +{ + Item::Type item_type = item->type(); + if (item_type == Item::Type::FIELD_ITEM) { + ctc_print_field(form, thd, (Item_ident*) item, out); + return; + } + + // for function item, in order to intercept all print() call to its args, a ctc_print_func is used instead of + // the built-in print() + if (item_type == Item::Type::FUNC_ITEM) { + std::string func_name_string = std::string(((Item_func *)item)->func_name()); + if (print_op_func_name.find(func_name_string) != print_op_func_name.end()) { + ctc_print_op(form, thd, (Item_func*) item, out); + } else if (item_func_printer.find(func_name_string) != item_func_printer.end()) { + ctc_item_print_t printer = item_func_printer[func_name_string]; + printer(form, thd, (Item_func *)item, out); + } else { + ctc_print_func(form, thd, (Item_func *)item, out); + } + return; + } + + // otherwise, it should be int literals / string literals etc, use the built-in print + // but with QT_WITHOUT_INTRODUCERS so we don't have introducers like _utf8mb4, which is not supported by ctsql + item->print(thd, out, enum_query_type(QT_WITHOUT_INTRODUCERS | QT_NO_DB | QT_NO_TABLE)); +} + +static int ctc_fill_func_key_part(TABLE *form, THD *thd, + TcDb__CtcDDLTableKeyPart *req_key_part, Value_generator *gcol_info) +{ + Item_func *func_expr_item = dynamic_cast(gcol_info->expr_item); + if (func_expr_item == nullptr) { my_printf_error(ER_DISALLOWED_OPERATION, "%s", MYF(0), - "Cantian does not support function indexes with multiple columns of arguments."); + "[CTC_CREATE_TABLE]: CTC do not support this functional index."); return CT_ERROR; - } - req_key_part->name = const_cast(args[i]->item_name.ptr()); - col_item_count++; } - } - - char buffer[FUNC_TEXT_MAX_LEN] = {0}; - String gc_expr(buffer, sizeof(buffer), &my_charset_bin); - gcol_info->print_expr(thd, &gc_expr); - string expr_str(buffer); - expr_str.erase(remove(expr_str.begin(), expr_str.end(), '`'), expr_str.end()); - // 处理json_value建索引,只允许returning char - // 不带returning默认char512 - if (strcmp(req_key_part->func_name, "json_value") == 0) { - std::regex reg_char("returning[ ]char[(]\\d+[)]"); - std::regex reg_charset("[_][a-z]+[0-9]*[a-z]*[0-9]*['$]"); - std::regex reg_charset2("[ ]character[ ]set[ ][a-z]+[0-9]*[a-z]*[0-9]"); - expr_str =std::regex_replace(expr_str, reg_char, "returning CLOB"); - expr_str =std::regex_replace(expr_str, reg_charset, "'"); - //处理char带charset设置 - expr_str =std::regex_replace(expr_str, reg_charset2, ""); - } - strncpy(req_key_part->func_text, expr_str.c_str(), FUNC_TEXT_MAX_LEN - 1); - return CT_SUCCESS; + + uint32_t arg_count = func_expr_item->arg_count; + if (arg_count == 0) { + my_printf_error(ER_DISALLOWED_OPERATION, "%s", MYF(0), + "[CTC_CREATE_TABLE]: There is no functional index."); + return CT_ERROR; + } + + if (ctc_check_func_name(std::string(func_expr_item->func_name())) != CT_SUCCESS) { + return CT_ERROR; + } + + req_key_part->is_func = true; + req_key_part->func_name = const_cast(func_expr_item->func_name()); + + uint32_t col_item_count = 0; + // recursively_get_dependency_item fill req_key_part->name + int result = recursively_get_dependency_item(form, func_expr_item, req_key_part, &col_item_count); + if (result != CT_SUCCESS || col_item_count != 1) { + my_printf_error(ER_DISALLOWED_OPERATION, "%s", MYF(0), + "[CTC_CREATE_TABLE]: CTC do not support this functional index."); + return CT_ERROR; + } + + char buffer[FUNC_TEXT_MAX_LEN] = {0}; + String gc_expr(buffer, sizeof(buffer), &my_charset_bin); + + gc_expr.length(0); + ctc_rewrite_expression_clause(form, thd, gcol_info->expr_item, &gc_expr); + strncpy(req_key_part->func_text, gc_expr.c_ptr(), FUNC_TEXT_MAX_LEN - 1); + return CT_SUCCESS; } static inline longlong get_session_level_create_index_parallelism(THD *thd) diff --git a/storage/ctc/ha_ctc_ddl.h b/storage/ctc/ha_ctc_ddl.h index 8d6bc1ac3a7dd4c45f424adfe8dcbcf46252f39f..af3bc487bd352f7a06e90f90296c1329f9bf841a 100644 --- a/storage/ctc/ha_ctc_ddl.h +++ b/storage/ctc/ha_ctc_ddl.h @@ -21,6 +21,8 @@ #include #include #include "storage/ctc/ha_ctcpart.h" +#include "sql/item_json_func.h" +#include "sql/table_function.h" #define UN_SUPPORT_DDL "ddl statement" /** Max table name length as defined in CT_MAX_NAME_LEN */ #define CTC_MAX_TABLE_NAME_LEN 64 @@ -172,6 +174,22 @@ static map mysql_collate_num_to_ctc_type = { {76, COLLATE_UTF8_TOLOWER_CI}, }; +typedef void (*ctc_item_print_t) (TABLE *form, const THD *thd, Item* item, String *out); +// the mapping relationship between rewroten Item sub-class print() + +// as for MySQL 8.0.26, only following sub class of Item would be using print_op +// 1. Item_func_int_div ("DIV"), +// 2. sub class of Item_num_op, +// * Item_func_div: / ; Item_func_mul: * ; Item_func_mod: % ; +// * Item_func_additive_op: Item_func_plus: + ; Item_func_minus: - +// 3. sub class of Item_func_bit, +// * Item_func_bit_two_param: Item_func_bit_or: | ; Item_func_bit_and: & ; Item_func_bit_xor: ^ +// * sub class of Item_func_shift: Item_func_shift_left: << ; Item_func_shift_right: >> +// * Item_func_bit_neg: ~ +static std::set print_op_func_name = { + "DIV", "/", "*", "%", "+", "-", "|", "&", "^", "<<", ">>", "~" +}; + typedef struct { char base_name[SMALL_RECORD_SIZE]; char var_name[SMALL_RECORD_SIZE];