diff --git a/debian/mariadb-client.install b/debian/mariadb-client.install index 28d1c0dff2b66..2bae9c5c04490 100644 --- a/debian/mariadb-client.install +++ b/debian/mariadb-client.install @@ -11,6 +11,7 @@ usr/bin/mariadb-dump usr/bin/mariadb-dumpslow usr/bin/mariadb-find-rows usr/bin/mariadb-fix-extensions +usr/bin/mariadb-frm usr/bin/mariadb-hotcopy usr/bin/mariadb-import usr/bin/mariadb-plugin diff --git a/extra/CMakeLists.txt b/extra/CMakeLists.txt index 2fcba58231576..9d0c5a97e160f 100644 --- a/extra/CMakeLists.txt +++ b/extra/CMakeLists.txt @@ -117,6 +117,9 @@ MYSQL_ADD_EXECUTABLE(perror perror.c) ADD_DEPENDENCIES(perror GenError) TARGET_LINK_LIBRARIES(perror mysys) +# FRM Parser utility +ADD_SUBDIRECTORY(mariadbfrm) + IF(UNIX) MYSQL_ADD_EXECUTABLE(resolveip resolveip.c) TARGET_LINK_LIBRARIES(resolveip mysys) diff --git a/extra/mariadbfrm/CMakeLists.txt b/extra/mariadbfrm/CMakeLists.txt new file mode 100644 index 0000000000000..a34d64d8a8813 --- /dev/null +++ b/extra/mariadbfrm/CMakeLists.txt @@ -0,0 +1,90 @@ +IF(NOT WITHOUT_SERVER) + + ADD_DEFINITIONS(-DFRM_PARSER) + + INCLUDE_DIRECTORIES( + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_BINARY_DIR}/sql + ${CMAKE_BINARY_DIR}/include + ${LIBFMT_INCLUDE_DIR} + ${PCRE_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} + ${SSL_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/tpool + ${CMAKE_SOURCE_DIR}/mysys + ${CMAKE_SOURCE_DIR}/strings + ) + + IF(WITH_WSREP) + INCLUDE_DIRECTORIES( + ${CMAKE_SOURCE_DIR}/wsrep-lib/include + ${CMAKE_SOURCE_DIR}/wsrep-lib/wsrep-API/v26 + ) + ENDIF() + + SET(FRM_PARSER_SOURCES + mariadb_frm.cc + frm_mocks.cc + ${CMAKE_SOURCE_DIR}/unittest/sql/dummy_builtins.cc + ) + + MYSQL_ADD_EXECUTABLE(mariadb-frm ${FRM_PARSER_SOURCES}) + TARGET_COMPILE_FEATURES(mariadb-frm PRIVATE cxx_std_20) + + IF(MSVC) + TARGET_COMPILE_OPTIONS(mariadb-frm PRIVATE + /wd4100 + /wd4204 + /wd4456 + /Gy + ) + ELSE() + TARGET_COMPILE_OPTIONS(mariadb-frm PRIVATE + -Wno-missing-field-initializers + -Wno-unused-label + ) + ENDIF() + + TARGET_COMPILE_DEFINITIONS(mariadb-frm PRIVATE + MYSQL_SERVER + ) + + TARGET_LINK_LIBRARIES(mariadb-frm + sql_builtins + mysys + strings + dbug + mysys_ssl + sql + ${ZLIB_LIBRARIES} + ${SSL_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ) + + IF(MSVC) + TARGET_LINK_OPTIONS(mariadb-frm PRIVATE + /INCREMENTAL:NO + /FORCE:MULTIPLE + /IGNORE:4006 + /IGNORE:4088 + /IGNORE:4217 + /WX:NO + ) + ELSEIF(APPLE) + TARGET_COMPILE_OPTIONS(mariadb-frm PRIVATE + -fno-common + ) + SET_TARGET_PROPERTIES(mariadb-frm PROPERTIES + LINK_FLAGS "-Wl,-w -Wl,-no_pie -Wl,-dead_strip -mmacosx-version-min=14.0" + ) + ELSE() + TARGET_LINK_OPTIONS(mariadb-frm PRIVATE -flto + "LINKER:--allow-multiple-definition" + "-ffunction-sections" + "LINKER:--gc-sections") + ENDIF() + + ADD_DEPENDENCIES(mariadb-frm GenError) + +ENDIF(NOT WITHOUT_SERVER) \ No newline at end of file diff --git a/extra/mariadbfrm/frm_mocks.cc b/extra/mariadbfrm/frm_mocks.cc new file mode 100644 index 0000000000000..c347287379309 --- /dev/null +++ b/extra/mariadbfrm/frm_mocks.cc @@ -0,0 +1,253 @@ +#include "handler.h" +#include "sql_plugin.h" +#include "sql_class.h" +#include "table.h" +#include "sql_error.h" +#include "log.h" +#include "create_options.h" + +class FRM_Mock_Handler : public handler +{ +public: + FRM_Mock_Handler(handlerton *hton_arg, TABLE_SHARE *share_arg) + : handler(hton_arg, share_arg) + { + cached_table_flags= FRM_Mock_Handler::table_flags(); + } + + int open(const char *name, int mode, uint test_if_locked) override + { + return 0; + } + int close() override { return 0; } + int write_row(const uchar *buf) override { return HA_ERR_WRONG_COMMAND; } + int update_row(const uchar *old_data, const uchar *new_data) override + { + return HA_ERR_WRONG_COMMAND; + } + int delete_row(const uchar *buf) override { return HA_ERR_WRONG_COMMAND; } + int index_read_map(uchar *buf, const uchar *key, key_part_map keypart_map, + ha_rkey_function find_flag) override + { + return HA_ERR_WRONG_COMMAND; + } + int index_next(uchar *buf) override { return HA_ERR_WRONG_COMMAND; } + int index_prev(uchar *buf) override { return HA_ERR_WRONG_COMMAND; } + int index_first(uchar *buf) override { return HA_ERR_WRONG_COMMAND; } + int index_last(uchar *buf) override { return HA_ERR_WRONG_COMMAND; } + int rnd_init(bool scan) override { return 0; } + int rnd_end() override { return 0; } + int rnd_next(uchar *buf) override { return HA_ERR_END_OF_FILE; } + int rnd_pos(uchar *buf, uchar *pos) override { return HA_ERR_WRONG_COMMAND; } + void position(const uchar *record) override {} + int info(uint flag) override { return 0; } + + [[nodiscard]] ulong index_flags(uint idx, uint part, + bool all_parts) const override + { + return (HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER | HA_READ_RANGE); + } + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, + thr_lock_type lock_type) override + { + return to; + } + int create(const char *name, TABLE *form, HA_CREATE_INFO *info) override + { + return HA_ERR_WRONG_COMMAND; + } + [[nodiscard]] const char *table_type() const override { return "FRM_MOCK"; } + [[nodiscard]] ulonglong table_flags() const override + { + return (HA_NO_TRANSACTIONS | HA_REC_NOT_IN_SEQ | HA_CAN_GEOMETRY | HA_CAN_VIRTUAL_COLUMNS); + } + [[nodiscard]] uint max_supported_key_length() const override { return 1000; } + [[nodiscard]] uint max_supported_key_part_length() const override + { + return 255; + } + int delete_all_rows() override { return HA_ERR_WRONG_COMMAND; } + ha_rows records_in_range(uint inx, const key_range *min_key, + const key_range *max_key, + page_range *pages) override + { + return 10; + } +}; + +static handler *frm_mock_create_handler(handlerton *hton, TABLE_SHARE *table, + MEM_ROOT *mem_root); + +static handlerton frm_mock_hton_struct= {}; + +static handler *frm_mock_create_handler(handlerton *hton, TABLE_SHARE *table, + MEM_ROOT *mem_root) +{ + return new (mem_root) FRM_Mock_Handler(hton, table); +} + +static void init_frm_mock_handlerton() +{ + frm_mock_hton_struct.create= frm_mock_create_handler; + frm_mock_hton_struct.db_type= DB_TYPE_UNKNOWN; + frm_mock_hton_struct.flags= HTON_NO_FLAGS; + frm_mock_hton_struct.slot= 0; + frm_mock_hton_struct.savepoint_offset= 0; +} + +handlerton *ha_default_handlerton_frm_mock(THD *thd) +{ + static bool initialized= false; + if (!initialized) + { + init_frm_mock_handlerton(); + initialized= true; + } + return &frm_mock_hton_struct; +} + +handlerton *get_frm_mock_handlerton() +{ + static bool initialized= false; + if (!initialized) + { + init_frm_mock_handlerton(); + initialized= true; + } + return &frm_mock_hton_struct; +} + +plugin_ref plugin_lock_frm_mock(THD *thd, plugin_ref ptr) +{ + return ptr; +} + +void plugin_unlock_frm_mock(THD *thd, plugin_ref ptr) +{ +} + +plugin_ref ha_resolve_by_name_frm_mock(THD *thd, const LEX_CSTRING *name, + bool is_temp_table) +{ + return global_system_variables.table_plugin; +} + +plugin_ref ha_lock_engine_frm_mock(THD *thd, const handlerton *hton) +{ + return global_system_variables.table_plugin; +} + +handler *get_new_handler_frm_mock(TABLE_SHARE *share, MEM_ROOT *alloc, + handlerton *db_type) +{ + return new (alloc) FRM_Mock_Handler(db_type, share); +} + +int error_log_print_frm_mock(enum loglevel level, const char *format, va_list args) +{ + if (level == WARNING_LEVEL) + { + vfprintf(stderr, format, args); + } + return 0; +} + +void push_warning_printf_frm_mock(THD *thd, Sql_condition::enum_warning_level level, + uint code, const char *format, va_list args) +{ +} + +bool engine_table_options_frm_read_frm_mock(const uchar *buf, size_t length, + TABLE_SHARE *share) +{ + return false; +} + +bool parse_engine_table_options_frm_mock(THD *thd, handlerton *ht, TABLE_SHARE *share) +{ + return false; +} + +void init_frm_mock_hooks() +{ + ha_default_handlerton_hook= ha_default_handlerton_frm_mock; + plugin_lock_hook= plugin_lock_frm_mock; + plugin_unlock_hook= plugin_unlock_frm_mock; + ha_resolve_by_name_hook= ha_resolve_by_name_frm_mock; + ha_lock_engine_hook= ha_lock_engine_frm_mock; + get_new_handler_hook= get_new_handler_frm_mock; + error_log_print_hook= error_log_print_frm_mock; + push_warning_printf_hook= push_warning_printf_frm_mock; + engine_table_options_frm_read_hook= engine_table_options_frm_read_frm_mock; + parse_engine_table_options_hook= parse_engine_table_options_frm_mock; +} + +extern "C" +{ +#ifdef SAFE_MUTEX + int safe_mutex_init(safe_mutex_t *mp, const pthread_mutexattr_t *attr, + const char *name, const char *file, uint line) + { + bzero(mp, sizeof(*mp)); + const int result= pthread_mutex_init(&mp->mutex, attr); + if (result == 0) + { + mp->file= file; + mp->line= line; + mp->name= name[0] == '&' ? name + 1 : name; + mp->count= 0; + mp->thread= 0; + mp->create_flags= MYF_NO_DEADLOCK_DETECTION; + } + return result; + } + + int safe_mutex_lock(safe_mutex_t *mp, myf my_flags, const char *file, + uint line) + { + int error; + if (my_flags & MYF_TRY_LOCK) + error= pthread_mutex_trylock(&mp->mutex); + else + error= pthread_mutex_lock(&mp->mutex); + + if (error == 0) + { + mp->thread= pthread_self(); + mp->count++; + mp->file= file; + mp->line= line; + } + return error; + } + + int safe_mutex_unlock(safe_mutex_t *mp, const char *file, uint line) + { + mp->thread= 0; + mp->count--; + return pthread_mutex_unlock(&mp->mutex); + } + + int safe_mutex_destroy(safe_mutex_t *mp, const char *file, uint line) + { + mp->file= nullptr; + return pthread_mutex_destroy(&mp->mutex); + } + + void safe_mutex_free_deadlock_data(safe_mutex_t *mp) {} + + int safe_cond_wait(pthread_cond_t *cond, safe_mutex_t *mp, const char *file, + uint line) + { + return pthread_cond_wait(cond, &mp->mutex); + } + + int safe_cond_timedwait(pthread_cond_t *cond, safe_mutex_t *mp, + const struct timespec *abstime, const char *file, + uint line) + { + return pthread_cond_timedwait(cond, &mp->mutex, abstime); + } +#endif // SAFE_MUTEX +} + diff --git a/extra/mariadbfrm/mariadb_frm.cc b/extra/mariadbfrm/mariadb_frm.cc new file mode 100644 index 0000000000000..cda4494441b16 --- /dev/null +++ b/extra/mariadbfrm/mariadb_frm.cc @@ -0,0 +1,658 @@ +/* Copyright (c) 2024, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +/** + @file extra/mariadb_frm.cc + FRM file parser utility - extracts table structure from .frm files +*/ + +#include "mariadb.h" + +#include "my_sys.h" +#include "mysql_com.h" +#include "m_ctype.h" +#include "field.h" +#include "sql_class.h" +#include "sql_show.h" +#include "table_cache.h" +#include "sql_table.h" +#include "sql_plugin.h" +#include "mysqld.h" +#ifdef WITH_WSREP +#include "wsrep_server_state.h" +#endif +#include +#include + +static bool debug_enabled= false; + +#define DEBUG(fmt, ...) \ + do \ + { \ + if (debug_enabled) \ + { \ + fprintf(stderr, "DEBUG: " fmt, ##__VA_ARGS__); \ + fflush(stderr); \ + } \ + } while (0) + +extern mysql_mutex_t LOCK_thread_id; +extern handlerton *get_frm_mock_handlerton(); +extern void init_frm_mock_hooks(); +extern bool global_frm_parser_mode; +extern PSI_mutex_key key_LOCK_thread_id; +extern mysql_mutex_t LOCK_plugin; +extern my_bool plugins_are_initialized; +extern st_plugin_int **plugin_array; +extern uint plugin_array_size; + +PSI_mutex_key key_LOCK_plugin; + +char server_uuid[37]= "12345678-1234-1234-1234-123456789012"; +ulong thread_id= 1; + +static st_maria_plugin mock_plugin= { + .type= MYSQL_STORAGE_ENGINE_PLUGIN, + .info= nullptr, + .name= "MOCK_ENGINE", + .author= "hp77", + .descr= "Mock storage engine for FRM parsing", + .license= PLUGIN_LICENSE_GPL, + .version= 0x0100, + .version_info= "1.0", + .maturity= MariaDB_PLUGIN_MATURITY_STABLE}; + +static st_plugin_int mock_plugin_int= { + .name= {const_cast("mock_storage_engine"), 19}, + .plugin= &mock_plugin, + .plugin_dl= nullptr, + .ptr_backup= nullptr, + .nbackups= 0, + .state= PLUGIN_IS_READY, + .ref_count= 1, + .locks_total= 0, + .data= nullptr, + .mem_root= {}, + .system_vars= nullptr, + .load_option= PLUGIN_ON}; + +static st_plugin_int *mock_plugin_ptr= &mock_plugin_int; +static plugin_ref mock_plugin_ref= plugin_int_to_ref(mock_plugin_ptr); + +static int init_thread_environment() +{ + mysql_mutex_init(key_LOCK_start_thread, &LOCK_start_thread, + MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_status, &LOCK_status, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_global_system_variables, + &LOCK_global_system_variables, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_user_conn, &LOCK_user_conn, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_thread_id, &LOCK_thread_id, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_start_thread, &COND_start_thread, nullptr); + + return 0; +} + +static int mysql_init_variables() +{ + auto &gv= global_system_variables; + gv.character_set_client= &my_charset_utf8mb3_general_ci; + gv.collation_connection= &my_charset_utf8mb3_general_ci; + gv.collation_database= &my_charset_utf8mb3_general_ci; + gv.character_set_results= &my_charset_utf8mb3_general_ci; + gv.character_set_filesystem= &my_charset_bin; + gv.table_plugin= mock_plugin_ref; + gv.tmp_table_plugin= mock_plugin_ref; + return 0; +} + +static int init_early_variables() +{ + sf_leaking_memory= 1; + mysqld_server_started= mysqld_server_initialized= 0; + default_charset_info= &my_charset_utf8mb3_general_ci; + return 0; +} + +static int init_character_sets() +{ + system_charset_info= &my_charset_utf8mb3_general_ci; + files_charset_info= &my_charset_utf8mb3_general_ci; + national_charset_info= &my_charset_utf8mb3_general_ci; + table_alias_charset= &my_charset_bin; + character_set_filesystem= &my_charset_bin; + default_charset_info= &my_charset_utf8mb3_general_ci; + return 0; +} + +static int init_plugin_system_complete() +{ + mysql_mutex_init(key_LOCK_plugin, &LOCK_plugin, MY_MUTEX_INIT_SLOW); + my_rnd_init(&sql_rand, static_cast(server_start_time), + static_cast(server_start_time) / 2); + handlerton *actual_hton= get_frm_mock_handlerton(); + + mock_plugin.info= actual_hton; + mock_plugin_int.data= actual_hton; + + actual_hton->flags= HTON_CAN_RECREATE; + actual_hton->db_type= DB_TYPE_BLACKHOLE_DB; + actual_hton->slot= 0; + hton2plugin[0]= &mock_plugin_int; + plugins_are_initialized= true; + + return 0; +} +/** + Initialize THD structure +*/ +static THD *create_minimal_thd() +{ + THD *thd= new THD(1, false); + + char stack_dummy; + thd->thread_stack= &stack_dummy; + thd->set_psi(nullptr); + + thd->push_internal_handler(new Turn_errors_to_warnings_handler()); + LEX *lex= new (thd->mem_root) LEX(); + thd->lex= lex; + lex_start(thd); + thd->variables.sql_mode= 0; + + thd->variables.sql_mode|= MODE_NO_ENGINE_SUBSTITUTION; + thd->variables.old_behavior= 0; + thd->variables.collation_server= default_charset_info; + + strncpy(thd->security_ctx->priv_user, "root", + sizeof(thd->security_ctx->priv_user) - 1); + thd->security_ctx->priv_user[sizeof(thd->security_ctx->priv_user) - 1]= '\0'; + strncpy(thd->security_ctx->priv_host, "localhost", + sizeof(thd->security_ctx->priv_host) - 1); + thd->security_ctx->priv_host[sizeof(thd->security_ctx->priv_host) - 1]= '\0'; + thd->security_ctx->host_or_ip= "localhost"; + + thd->stmt_arena= thd; + thd->set_stmt_da(new Diagnostics_area(false)); + // for geometric types + bzero(&thd->status_var, sizeof(thd->status_var)); + bzero(&thd->org_status_var, sizeof(thd->org_status_var)); + thd->status_var.flush_status_time= my_time(0); + + lex->sql_command= SQLCOM_SHOW_CREATE; + lex->create_info.init(); + + return thd; +} + +static int init_sql_functions() +{ + if (item_create_init()) + return 1; + return 0; +} + +static void cleanup_sql_functions() { item_create_cleanup(); } + +/** + Read FRM file into memory +*/ +static uchar *read_frm_file(const char *filename, size_t *length) +{ + File file; + MY_STAT stat_info{}; + uchar *buffer= nullptr; + + if (my_stat(filename, &stat_info, MYF(0)) == nullptr) + { + DEBUG("Error: Cannot stat file '%s': %s\n", filename, strerror(errno)); + return nullptr; + } + + *length= stat_info.st_size; + + if (!(buffer= static_cast( + my_malloc(PSI_NOT_INSTRUMENTED, *length, MYF(MY_WME))))) + { + DEBUG("Error: Cannot allocate memory for FRM file\n"); + return nullptr; + } + + if ((file= mysql_file_open(key_file_frm, filename, O_RDONLY | O_SHARE, + MYF(0))) < 0) + { + DEBUG("Error: Cannot open file '%s': %s\n", filename, strerror(errno)); + my_free(buffer); + return nullptr; + } + + if (mysql_file_read(file, buffer, *length, MYF(MY_NABP))) + { + DEBUG("Error: Cannot read file '%s': %s\n", filename, strerror(errno)); + my_free(buffer); + mysql_file_close(file, MYF(0)); + return nullptr; + } + + mysql_file_close(file, MYF(0)); + return buffer; +} + +/** + Map legacy DB type to engine name +*/ +static const char * +get_engine_name_from_legacy_type(enum legacy_db_type db_type) +{ + switch (db_type) + { + case DB_TYPE_MYISAM: + return "MyISAM"; + case DB_TYPE_INNODB: + return "InnoDB"; + case DB_TYPE_ARIA: + return "Aria"; + case DB_TYPE_ARCHIVE_DB: + return "ARCHIVE"; + case DB_TYPE_CSV_DB: + return "CSV"; + case DB_TYPE_HEAP: + return "MEMORY"; + case DB_TYPE_BLACKHOLE_DB: + return "BLACKHOLE"; + case DB_TYPE_FEDERATED_DB: + return "FEDERATED"; + case DB_TYPE_MRG_MYISAM: + return "MRG_MyISAM"; + case DB_TYPE_PARTITION_DB: + return "partition"; + case DB_TYPE_SEQUENCE: + return "SEQUENCE"; + case DB_TYPE_S3: + return "S3"; + default: + return "UNKNOWN"; + } +} + +/** + Extract database and table name from FRM file path +*/ +static bool extract_db_table_names(const char *frm_path, LEX_CSTRING *db_name, + LEX_CSTRING *table_name) +{ + char *path_copy, *db_start, *table_start, *frm_ext; + + if (!((path_copy= my_strdup(PSI_NOT_INSTRUMENTED, frm_path, MYF(MY_WME))))) + return true; + + if (!((frm_ext= strstr(path_copy, ".frm")))) + { + my_free(path_copy); + return true; + } + *frm_ext= '\0'; + + table_start= strrchr(path_copy, '/'); + if (!table_start) + table_start= strrchr(path_copy, '\\'); + + if (!table_start) + { + table_name->str= my_strdup(PSI_NOT_INSTRUMENTED, path_copy, MYF(MY_WME)); + table_name->length= strlen(table_name->str); + db_name->str= my_strdup(PSI_NOT_INSTRUMENTED, "test", MYF(MY_WME)); + db_name->length= 4; + my_free(path_copy); + return false; + } + + *table_start= '\0'; + table_start++; + + db_start= strrchr(path_copy, '/'); + if (!db_start) + db_start= strrchr(path_copy, '\\'); + + if (!db_start) + db_start= path_copy; + else + db_start++; + + table_name->str= my_strdup(PSI_NOT_INSTRUMENTED, table_start, MYF(MY_WME)); + table_name->length= strlen(table_name->str); + db_name->str= my_strdup(PSI_NOT_INSTRUMENTED, db_start, MYF(MY_WME)); + db_name->length= strlen(db_name->str); + + my_free(path_copy); + return false; +} + +/** + Parse FRM file and create TABLE_SHARE and TABLE structures +*/ +static int parse_frm_file(THD *fake_thd, const char *frm_path) +{ + DEBUG("Entering parse_frm_file\n"); + + // Stack-allocated structures + TABLE_LIST table_list; + TABLE_SHARE share{}; + TABLE table{}; + TDC_element tdc{.ref_count= 1}; + size_t frm_length= 0; + uchar *frm_data_raw; + LEX_CSTRING db_name{}, table_name{}; + int open_result, show_result; + legacy_db_type real_engine_type; + + DEBUG("table_list initialized\n"); + DEBUG("About to read FRM file: %s\n", frm_path); + + // Read FRM file with smart pointer + frm_data_raw= read_frm_file(frm_path, &frm_length); + if (!frm_data_raw) + { + DEBUG("Failed to read FRM file\n"); + return 1; + } + + // Use unique_ptr with my_free directly as deleter + std::unique_ptr frm_data(frm_data_raw, my_free); + + if (extract_db_table_names(frm_path, &db_name, &table_name)) + { + DEBUG("Error: Cannot extract database and table names from path\n"); + return 1; + } + + std::unique_ptr db_name_ptr( + const_cast(db_name.str), my_free); + std::unique_ptr table_name_ptr( + const_cast(table_name.str), my_free); + table_list.init_one_table(&db_name, &table_name, &table_name, TL_READ); + + DEBUG("Names extracted - db: %.*s, table: %.*s\n", + static_cast(db_name.length), db_name.str, (int) table_name.length, + table_name.str); + + DEBUG("Initializing stack-allocated TABLE_SHARE\n"); + + mysql_mutex_init(0, &share.LOCK_share, MY_MUTEX_INIT_FAST); + + share.db.str= db_name.str; + share.db.length= db_name.length; + share.table_name.str= table_name.str; + share.table_name.length= table_name.length; + share.tdc= &tdc; + + init_alloc_root(PSI_NOT_INSTRUMENTED, &share.mem_root, 1024, 0, MYF(0)); + + fake_thd->lex->sql_command= SQLCOM_SHOW_CREATE; + strncpy(fake_thd->security_ctx->priv_user, "root", + sizeof(fake_thd->security_ctx->priv_user) - 1); + fake_thd->security_ctx + ->priv_user[sizeof(fake_thd->security_ctx->priv_user) - 1]= '\0'; + + DEBUG("About to call init_from_binary_frm_image\n"); + size_t key_length= db_name.length + 1 + table_name.length + 1; + if (auto key_buff= + static_cast(alloc_root(&share.mem_root, key_length))) + { + memcpy(key_buff, db_name.str, db_name.length); + key_buff[db_name.length]= '\0'; + memcpy(key_buff + db_name.length + 1, table_name.str, table_name.length); + key_buff[db_name.length + 1 + table_name.length]= '\0'; + + share.table_cache_key.str= key_buff; + share.table_cache_key.length= key_length; + } + char path_buff[512]; + snprintf(path_buff, sizeof(path_buff), "%s/%s", db_name.str, table_name.str); + if (auto norm_path= static_cast( + alloc_root(&share.mem_root, strlen(path_buff) + 1))) + { + strcpy(norm_path, path_buff); + share.normalized_path.str= norm_path; + share.normalized_path.length= strlen(norm_path); + } + + share.path= share.normalized_path; + share.table_category= TABLE_CATEGORY_USER; + share.tmp_table= NO_TMP_TABLE; + share.db_plugin= mock_plugin_ref; + + share.field= nullptr; + share.fields= 0; + THD *saved_current_thd= current_thd; + set_current_thd(fake_thd); + + auto cleanup_mem_root= [&]() { free_root(&share.mem_root, MYF(0)); }; + std::unique_ptr> mem_root_cleanup( + &share.mem_root, [&](void *) { cleanup_mem_root(); }); + + int parse_error= share.init_from_binary_frm_image( + fake_thd, false, frm_data.get(), frm_length, nullptr, 0, true); + set_current_thd(saved_current_thd); + + if (parse_error != 0) + { + DEBUG("Error: Cannot parse FRM file - init_from_binary_frm_image failed " + "with error %d: %s\n", + my_errno, strerror(my_errno)); + return 1; + } + + DEBUG("init_from_binary_frm_image completed successfully\n"); + + set_current_thd(fake_thd); + open_result= + open_table_from_share(fake_thd, &share, &table_name, HA_OPEN_KEYFILE, + EXTRA_RECORD, 0, &table, false, nullptr, true); + if (open_result) + { + DEBUG("Error: open_table_from_share failed with error %d\n", open_result); + return open_result; + } + + table.s= &share; + table.in_use= fake_thd; + + table_list.table_name= Lex_ident_table(table_name); + table_list.db= Lex_ident_db(db_name); + table_list.alias= Lex_ident_table(table_name); + table_list.table= &table; + + if (!table.file) + { + handlerton *hton= get_frm_mock_handlerton(); + if (hton && hton->create) + { + table.file= hton->create(hton, &share, &table.mem_root); + if (table.file) + { + table.file->init(); + } + } + } + + String ddl_buffer; + show_result= show_create_table(fake_thd, &table_list, &ddl_buffer, nullptr, + WITHOUT_DB_NAME); + if (show_result) + { + DEBUG("Error: open_table_from_share failed with error %d\n", show_result); + return show_result; + } + real_engine_type= static_cast((uint) frm_data.get()[3]); + const char *real_engine_name= + get_engine_name_from_legacy_type(real_engine_type); + + const char *original_ddl= ddl_buffer.c_ptr(); + if (const char *mock_pos= strstr(original_ddl, "mock_storage_engine")) + { + String corrected_ddl; + corrected_ddl.append(original_ddl, mock_pos - original_ddl); + corrected_ddl.append(real_engine_name, strlen(real_engine_name)); + const char *remainder= mock_pos + strlen("mock_storage_engine"); + corrected_ddl.append(remainder, strlen(remainder)); + printf("%s\n", corrected_ddl.c_ptr()); + } + else + { + printf("%s\n", original_ddl); + } + + set_current_thd(saved_current_thd); + return 0; +} + +void cleanup() +{ + /* + Do not explicitly delete type_handler_data here. In SAFEMALLOC debug + builds (SAFEMALLOC + SAFE_MUTEX), the Dynamic_array buffers inside + Type_handler_data interact with the safemalloc heap-tracking linked + list in a way that produces heap corruption ("corrupted size vs. + prev_size") when free() is called on the st_irem metadata block. + This is a side-effect of the partial initialisation environment of + mariadb-frm (mocked hooks, no full server plugin/PSI init). + Since mariadb-frm is a short-lived CLI tool, we leave the object + alive and rely on the OS to reclaim memory at process exit. + sf_leaking_memory was set to 1 in init_early_variables(), so + safemalloc's atexit handler will not report this as a leak. + */ + type_handler_data= nullptr; + cleanup_sql_functions(); + my_thread_end(); + mysql_mutex_destroy(&LOCK_start_thread); + mysql_mutex_destroy(&LOCK_status); + mysql_mutex_destroy(&LOCK_global_system_variables); + mysql_mutex_destroy(&LOCK_user_conn); + mysql_mutex_destroy(&LOCK_thread_id); + mysql_cond_destroy(&COND_start_thread); +#ifdef WITH_WSREP + Wsrep_server_state::destroy(); +#endif + my_end(0); +} + +/** + Parse command line arguments +*/ +static bool parse_arguments(int argc, char **argv, const char **frm_file) +{ + *frm_file= nullptr; + + for (int i= 1; i < argc; i++) + { + if (strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) + { + debug_enabled= true; + } + else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) + { + printf("Usage: %s [OPTIONS] \n", argv[0]); + printf("Extract table structure from .frm files\n\n"); + printf("Options:\n"); + printf(" -d, --debug Enable debug output\n"); + printf(" -h, --help Show this help message\n\n"); + printf("Example:\n"); + printf(" %s table.frm\n", argv[0]); + printf(" %s --debug table.frm\n", argv[0]); + return false; + } + else if (argv[i][0] == '-') + { + fprintf(stderr, "Error: Unknown option '%s'\n", argv[i]); + fprintf(stderr, "Use --help for usage information\n"); + return false; + } + else + { + if (*frm_file != nullptr) + { + fprintf(stderr, "Error: Multiple FRM files specified\n"); + return false; + } + *frm_file= argv[i]; + } + } + + if (*frm_file == nullptr) + { + fprintf(stderr, "Error: No FRM file specified\n"); + fprintf(stderr, "Usage: %s [OPTIONS] \n", argv[0]); + fprintf(stderr, "Use --help for more information\n"); + return false; + } + + return true; +} + +/** + Main function +*/ +int main(int argc, char **argv) +{ + init_frm_mock_hooks(); + global_frm_parser_mode= true; +#ifdef WITH_WSREP + Wsrep_server_state::init_once("mariadb-frm", "", "", "", wsrep::gtid(), 0); +#endif + int exit_code{}; + const char *frm_file; + + if (!parse_arguments(argc, argv, &frm_file)) + { + return 1; + } + + DEBUG("Starting frm_parser...\n"); + MY_INIT(argv[0]); + DEBUG("MY_INIT completed\n"); + + DEBUG("Arguments validated, FRM file: %s\n", frm_file); + + init_character_sets(); + init_thread_environment(); + init_early_variables(); + mysql_init_variables(); + init_sql_functions(); + + if (!(type_handler_data= new Type_handler_data) || type_handler_data->init()) + { + DEBUG("Error: Cannot initialize type handler system\n"); + return 1; + } + init_plugin_system_complete(); + if (my_thread_init()) + { + DEBUG("Error: Cannot initialize required thread subsystems\n"); + return 1; + } + + auto thd_deleter= [](THD *) {}; + std::unique_ptr fake_thd(create_minimal_thd(), + thd_deleter); + + DEBUG("THD initialized successfully, about to parse FRM file...\n"); + + exit_code= parse_frm_file(fake_thd.get(), frm_file); + + DEBUG("FRM parsing completed with exit code: %d\n", exit_code); + cleanup(); + return exit_code; +} diff --git a/mysql-test/mariadb-test-run.pl b/mysql-test/mariadb-test-run.pl index 788927069015e..3af83ab7eb129 100755 --- a/mysql-test/mariadb-test-run.pl +++ b/mysql-test/mariadb-test-run.pl @@ -232,6 +232,7 @@ END our $exe_libtool; our $exe_mysql_embedded; our $exe_mariadb_conv; +our $exe_mariadb_frm; our $opt_big_test= 0; our $opt_staging_run= 0; @@ -1943,7 +1944,9 @@ () $exe_mysql= mtr_exe_exists("$path_client_bindir/mariadb"); $exe_mysql_plugin= mtr_exe_exists("$path_client_bindir/mariadb-plugin"); $exe_mariadb_conv= mtr_exe_exists("$path_client_bindir/mariadb-conv"); - + $exe_mariadb_frm= mtr_exe_maybe_exists("$bindir/extra$multiconfig/mariadbfrm/mariadb-frm", + "$bindir/extra/mariadbfrm$multiconfig/mariadb-frm", + "$path_client_bindir/mariadbfrm/mariadb-frm"); $exe_mysql_embedded= mtr_exe_maybe_exists("$bindir/libmysqld/examples/mariadb-embedded", "$bindir/libmysqld/examples/mysql_embedded"); @@ -2235,6 +2238,7 @@ sub environment_setup { $ENV{'MYSQL_PLUGIN'}= $exe_mysql_plugin; $ENV{'MYSQL_EMBEDDED'}= $exe_mysql_embedded; $ENV{'MARIADB_CONV'}= "$exe_mariadb_conv --character-sets-dir=$path_charsetsdir"; + $ENV{'MARIADB_FRM'}= $exe_mariadb_frm; if(IS_WINDOWS) { $ENV{'MYSQL_INSTALL_DB_EXE'}= mtr_exe_exists("$bindir/sql$multiconfig/mariadb-install-db", diff --git a/mysql-test/std_data/frm/t_archive.frm b/mysql-test/std_data/frm/t_archive.frm new file mode 100644 index 0000000000000..f2e79e6098a06 Binary files /dev/null and b/mysql-test/std_data/frm/t_archive.frm differ diff --git a/mysql-test/std_data/frm/table_simple.frm b/mysql-test/std_data/frm/table_simple.frm new file mode 100644 index 0000000000000..504543af28ce5 Binary files /dev/null and b/mysql-test/std_data/frm/table_simple.frm differ diff --git a/mysql-test/suite/client/mariadb-frm-advanced.result b/mysql-test/suite/client/mariadb-frm-advanced.result new file mode 100644 index 0000000000000..b5a9cdfab2ea9 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-advanced.result @@ -0,0 +1,256 @@ +MariaDB frm parser test - Advanced Features +Testing advanced MariaDB features and complex scenarios +CREATE TABLE t_charsets ( +id INT, +utf8_col VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci, +utf8mb4_col VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, +latin1_col VARCHAR(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci, +binary_col VARCHAR(100) CHARACTER SET binary +) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE TABLE `t_charsets` ( + `id` int(11) DEFAULT NULL, + `utf8_col` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, + `utf8mb4_col` varchar(100) DEFAULT NULL, + `latin1_col` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL, + `binary_col` varbinary(100) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci +DROP TABLE t_charsets; +CREATE SEQUENCE s1 START WITH 1000 INCREMENT BY 10; +CREATE TABLE t_sequence ( +id INT DEFAULT NEXTVAL(s1), +name VARCHAR(100), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE TABLE `t_sequence` ( + `id` int(11) DEFAULT nextval(`test`.`s1`), + `name` varchar(100) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_sequence; +DROP SEQUENCE s1; +CREATE TABLE t_parent ( +id INT PRIMARY KEY, +name VARCHAR(100) +) ENGINE=InnoDB; +CREATE TABLE t_child ( +id INT PRIMARY KEY, +parent_id INT, +data VARCHAR(255), +FOREIGN KEY fk_parent (parent_id) REFERENCES t_parent(id) ON DELETE CASCADE ON UPDATE RESTRICT +) ENGINE=InnoDB; +CREATE TABLE `t_child` ( + `id` int(11) NOT NULL, + `parent_id` int(11) DEFAULT NULL, + `data` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `fk_parent` (`parent_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_child; +DROP TABLE t_parent; +CREATE TABLE t_check_constraints ( +id INT, +age INT CHECK (age >= 0 AND age <= 150), +email VARCHAR(255) CHECK (email LIKE '%@%.%'), +score DECIMAL(5,2) CHECK (score BETWEEN 0.00 AND 100.00), +status VARCHAR(20) CHECK (status IN ('active', 'inactive', 'pending')) +); +CREATE TABLE `t_check_constraints` ( + `id` int(11) DEFAULT NULL, + `age` int(11) DEFAULT NULL CHECK (`age` >= 0 and `age` <= 150), + `email` varchar(255) DEFAULT NULL CHECK (`email` like '%@%.%'), + `score` decimal(5,2) DEFAULT NULL CHECK (`score` between 0.00 and 100.00), + `status` varchar(20) DEFAULT NULL CHECK (`status` in ('active','inactive','pending')) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_check_constraints; +CREATE TABLE t_invisible_columns ( +id INT, +name VARCHAR(100), +secret_data VARCHAR(255) INVISIBLE, +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP INVISIBLE, +visible_data TEXT +); +CREATE TABLE `t_invisible_columns` ( + `id` int(11) DEFAULT NULL, + `name` varchar(100) DEFAULT NULL, + `secret_data` varchar(255) DEFAULT NULL INVISIBLE, + `created_at` timestamp NULL DEFAULT current_timestamp(0) INVISIBLE, + `visible_data` text DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_invisible_columns; +CREATE TABLE t_system_versioned ( +id INT PRIMARY KEY, +name VARCHAR(100), +data TEXT +) WITH SYSTEM VERSIONING; +CREATE TABLE `t_system_versioned` ( + `id` int(11) NOT NULL, + `name` varchar(100) DEFAULT NULL, + `data` text DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING +DROP TABLE t_system_versioned; +CREATE TABLE t_application_time ( +id INT PRIMARY KEY, +name VARCHAR(100), +valid_from DATE, +valid_to DATE, +PERIOD FOR application_time (valid_from, valid_to) +); +CREATE TABLE `t_application_time` ( + `id` int(11) NOT NULL, + `name` varchar(100) DEFAULT NULL, + `valid_from` date NOT NULL, + `valid_to` date NOT NULL, + PERIOD FOR `application_time` (`valid_from`, `valid_to`), + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_application_time; +CREATE TABLE t_with_triggers ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(100), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); +CREATE TRIGGER t_with_triggers_before_update +BEFORE UPDATE ON t_with_triggers +FOR EACH ROW +SET NEW.updated_at = NOW(); +CREATE TABLE `t_with_triggers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0), + `updated_at` timestamp NULL DEFAULT current_timestamp(0) ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TRIGGER t_with_triggers_before_update; +DROP TABLE t_with_triggers; +CREATE TABLE t_geometry ( +id INT PRIMARY KEY, +location POINT NOT NULL, +boundary POLYGON NOT NULL, +path LINESTRING NOT NULL, +region MULTIPOLYGON NOT NULL, +SPATIAL INDEX idx_location (location), +SPATIAL INDEX idx_boundary (boundary) +) ENGINE=MyISAM; +CREATE TABLE `t_geometry` ( + `id` int(11) NOT NULL, + `location` point NOT NULL, + `boundary` polygon NOT NULL, + `path` linestring NOT NULL, + `region` multipolygon NOT NULL, + PRIMARY KEY (`id`), + SPATIAL KEY `idx_location` (`location`), + SPATIAL KEY `idx_boundary` (`boundary`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_geometry; +CREATE TABLE t_compressed ( +id INT PRIMARY KEY, +data LONGTEXT, +json_data JSON +) ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8; +CREATE TABLE `t_compressed` ( + `id` int(11) NOT NULL, + `data` longtext DEFAULT NULL, + `json_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`json_data`)), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8 +DROP TABLE t_compressed; +CREATE TABLE t_many_indexes ( +id INT, +col1 VARCHAR(50), +col2 VARCHAR(50), +col3 VARCHAR(50), +col4 VARCHAR(50), +col5 VARCHAR(50), +col6 INT, +col7 INT, +col8 DATE, +col9 TIMESTAMP, +col10 DECIMAL(10,2), +INDEX idx1 (col1), +INDEX idx2 (col2), +INDEX idx3 (col3), +INDEX idx4 (col4), +INDEX idx5 (col5), +INDEX idx6 (col6), +INDEX idx7 (col7), +INDEX idx8 (col8), +INDEX idx9 (col9), +INDEX idx10 (col10), +INDEX idx_compound1 (col1, col2), +INDEX idx_compound2 (col3, col4, col5), +INDEX idx_compound3 (col6, col7, col8) +); +CREATE TABLE `t_many_indexes` ( + `id` int(11) DEFAULT NULL, + `col1` varchar(50) DEFAULT NULL, + `col2` varchar(50) DEFAULT NULL, + `col3` varchar(50) DEFAULT NULL, + `col4` varchar(50) DEFAULT NULL, + `col5` varchar(50) DEFAULT NULL, + `col6` int(11) DEFAULT NULL, + `col7` int(11) DEFAULT NULL, + `col8` date DEFAULT NULL, + `col9` timestamp NULL DEFAULT NULL, + `col10` decimal(10,2) DEFAULT NULL, + KEY `idx1` (`col1`), + KEY `idx2` (`col2`), + KEY `idx3` (`col3`), + KEY `idx4` (`col4`), + KEY `idx5` (`col5`), + KEY `idx6` (`col6`), + KEY `idx7` (`col7`), + KEY `idx8` (`col8`), + KEY `idx9` (`col9`), + KEY `idx10` (`col10`), + KEY `idx_compound1` (`col1`,`col2`), + KEY `idx_compound2` (`col3`,`col4`,`col5`), + KEY `idx_compound3` (`col6`,`col7`,`col8`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_many_indexes; +CREATE TABLE t_wide_table ( +id INT PRIMARY KEY, +col001 VARCHAR(50), col002 VARCHAR(50), col003 VARCHAR(50), col004 VARCHAR(50), col005 VARCHAR(50), +col006 VARCHAR(50), col007 VARCHAR(50), col008 VARCHAR(50), col009 VARCHAR(50), col010 VARCHAR(50), +col011 INT, col012 INT, col013 INT, col014 INT, col015 INT, +col016 DATE, col017 DATE, col018 DATE, col019 DATE, col020 DATE, +col021 TIMESTAMP, col022 TIMESTAMP, col023 TIMESTAMP, col024 TIMESTAMP, col025 TIMESTAMP, +col026 DECIMAL(10,2), col027 DECIMAL(10,2), col028 DECIMAL(10,2), col029 DECIMAL(10,2), col030 DECIMAL(10,2) +); +CREATE TABLE `t_wide_table` ( + `id` int(11) NOT NULL, + `col001` varchar(50) DEFAULT NULL, + `col002` varchar(50) DEFAULT NULL, + `col003` varchar(50) DEFAULT NULL, + `col004` varchar(50) DEFAULT NULL, + `col005` varchar(50) DEFAULT NULL, + `col006` varchar(50) DEFAULT NULL, + `col007` varchar(50) DEFAULT NULL, + `col008` varchar(50) DEFAULT NULL, + `col009` varchar(50) DEFAULT NULL, + `col010` varchar(50) DEFAULT NULL, + `col011` int(11) DEFAULT NULL, + `col012` int(11) DEFAULT NULL, + `col013` int(11) DEFAULT NULL, + `col014` int(11) DEFAULT NULL, + `col015` int(11) DEFAULT NULL, + `col016` date DEFAULT NULL, + `col017` date DEFAULT NULL, + `col018` date DEFAULT NULL, + `col019` date DEFAULT NULL, + `col020` date DEFAULT NULL, + `col021` timestamp NULL DEFAULT NULL, + `col022` timestamp NULL DEFAULT NULL, + `col023` timestamp NULL DEFAULT NULL, + `col024` timestamp NULL DEFAULT NULL, + `col025` timestamp NULL DEFAULT NULL, + `col026` decimal(10,2) DEFAULT NULL, + `col027` decimal(10,2) DEFAULT NULL, + `col028` decimal(10,2) DEFAULT NULL, + `col029` decimal(10,2) DEFAULT NULL, + `col030` decimal(10,2) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_wide_table; +Advanced features test completed diff --git a/mysql-test/suite/client/mariadb-frm-advanced.test b/mysql-test/suite/client/mariadb-frm-advanced.test new file mode 100644 index 0000000000000..ca8c074a998c4 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-advanced.test @@ -0,0 +1,184 @@ +--echo MariaDB frm parser test - Advanced Features +--echo Testing advanced MariaDB features and complex scenarios + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` + +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} + +# Test character sets and collations +CREATE TABLE t_charsets ( + id INT, + utf8_col VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci, + utf8mb4_col VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + latin1_col VARCHAR(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci, + binary_col VARCHAR(100) CHARACTER SET binary +) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_charsets.frm +DROP TABLE t_charsets; + +# Test sequences (if supported) +CREATE SEQUENCE s1 START WITH 1000 INCREMENT BY 10; +CREATE TABLE t_sequence ( + id INT DEFAULT NEXTVAL(s1), + name VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_sequence.frm +DROP TABLE t_sequence; +DROP SEQUENCE s1; + +# Test foreign keys (InnoDB) +--source include/have_innodb.inc +CREATE TABLE t_parent ( + id INT PRIMARY KEY, + name VARCHAR(100) +) ENGINE=InnoDB; + +CREATE TABLE t_child ( + id INT PRIMARY KEY, + parent_id INT, + data VARCHAR(255), + FOREIGN KEY fk_parent (parent_id) REFERENCES t_parent(id) ON DELETE CASCADE ON UPDATE RESTRICT +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_child.frm +DROP TABLE t_child; +DROP TABLE t_parent; + +# Test check constraints (MariaDB 10.2+) +CREATE TABLE t_check_constraints ( + id INT, + age INT CHECK (age >= 0 AND age <= 150), + email VARCHAR(255) CHECK (email LIKE '%@%.%'), + score DECIMAL(5,2) CHECK (score BETWEEN 0.00 AND 100.00), + status VARCHAR(20) CHECK (status IN ('active', 'inactive', 'pending')) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_check_constraints.frm +DROP TABLE t_check_constraints; + +# Test invisible columns (MariaDB 10.3+) +CREATE TABLE t_invisible_columns ( + id INT, + name VARCHAR(100), + secret_data VARCHAR(255) INVISIBLE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP INVISIBLE, + visible_data TEXT +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_invisible_columns.frm +DROP TABLE t_invisible_columns; + +# Test system versioning (MariaDB 10.3+) +CREATE TABLE t_system_versioned ( + id INT PRIMARY KEY, + name VARCHAR(100), + data TEXT +) WITH SYSTEM VERSIONING; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_system_versioned.frm +DROP TABLE t_system_versioned; + +# Test application-time periods (MariaDB 10.4+) +CREATE TABLE t_application_time ( + id INT PRIMARY KEY, + name VARCHAR(100), + valid_from DATE, + valid_to DATE, + PERIOD FOR application_time (valid_from, valid_to) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_application_time.frm +DROP TABLE t_application_time; + +# Test complex triggers and procedures (should not crash) +CREATE TABLE t_with_triggers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TRIGGER t_with_triggers_before_update + BEFORE UPDATE ON t_with_triggers + FOR EACH ROW + SET NEW.updated_at = NOW(); + + +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_with_triggers.frm +DROP TRIGGER t_with_triggers_before_update; +DROP TABLE t_with_triggers; + +# Test geometry data types (spatial extensions) +CREATE TABLE t_geometry ( + id INT PRIMARY KEY, + location POINT NOT NULL, + boundary POLYGON NOT NULL, + path LINESTRING NOT NULL, + region MULTIPOLYGON NOT NULL, + SPATIAL INDEX idx_location (location), + SPATIAL INDEX idx_boundary (boundary) +) ENGINE=MyISAM; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_geometry.frm +DROP TABLE t_geometry; + +# Test compressed tables (different row formats) +--source include/have_innodb.inc +CREATE TABLE t_compressed ( + id INT PRIMARY KEY, + data LONGTEXT, + json_data JSON +) ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_compressed.frm +DROP TABLE t_compressed; + +# Test table with many indexes (edge case) +CREATE TABLE t_many_indexes ( + id INT, + col1 VARCHAR(50), + col2 VARCHAR(50), + col3 VARCHAR(50), + col4 VARCHAR(50), + col5 VARCHAR(50), + col6 INT, + col7 INT, + col8 DATE, + col9 TIMESTAMP, + col10 DECIMAL(10,2), + INDEX idx1 (col1), + INDEX idx2 (col2), + INDEX idx3 (col3), + INDEX idx4 (col4), + INDEX idx5 (col5), + INDEX idx6 (col6), + INDEX idx7 (col7), + INDEX idx8 (col8), + INDEX idx9 (col9), + INDEX idx10 (col10), + INDEX idx_compound1 (col1, col2), + INDEX idx_compound2 (col3, col4, col5), + INDEX idx_compound3 (col6, col7, col8) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_many_indexes.frm +DROP TABLE t_many_indexes; + +# Test very wide table (many columns) +CREATE TABLE t_wide_table ( + id INT PRIMARY KEY, + col001 VARCHAR(50), col002 VARCHAR(50), col003 VARCHAR(50), col004 VARCHAR(50), col005 VARCHAR(50), + col006 VARCHAR(50), col007 VARCHAR(50), col008 VARCHAR(50), col009 VARCHAR(50), col010 VARCHAR(50), + col011 INT, col012 INT, col013 INT, col014 INT, col015 INT, + col016 DATE, col017 DATE, col018 DATE, col019 DATE, col020 DATE, + col021 TIMESTAMP, col022 TIMESTAMP, col023 TIMESTAMP, col024 TIMESTAMP, col025 TIMESTAMP, + col026 DECIMAL(10,2), col027 DECIMAL(10,2), col028 DECIMAL(10,2), col029 DECIMAL(10,2), col030 DECIMAL(10,2) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_wide_table.frm +DROP TABLE t_wide_table; + +--echo Advanced features test completed diff --git a/mysql-test/suite/client/mariadb-frm-comments.result b/mysql-test/suite/client/mariadb-frm-comments.result new file mode 100644 index 0000000000000..ad4504897e19c --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-comments.result @@ -0,0 +1,91 @@ +MariaDB frm parser test - Comments +Testing field-level and table-level comments +CREATE TABLE t_field_comments ( +id INT COMMENT 'Primary key identifier', +name VARCHAR(100) COMMENT 'Full name of the user', +email VARCHAR(255) COMMENT 'User email address (must be unique)', +age INT COMMENT 'Age in years', +salary DECIMAL(10,2) COMMENT 'Annual salary in USD', +is_active BOOLEAN COMMENT 'Whether the user account is active', +created_at TIMESTAMP COMMENT 'When the record was created' +); +CREATE TABLE `t_field_comments` ( + `id` int(11) DEFAULT NULL COMMENT 'Primary key identifier', + `name` varchar(100) DEFAULT NULL COMMENT 'Full name of the user', + `email` varchar(255) DEFAULT NULL COMMENT 'User email address (must be unique)', + `age` int(11) DEFAULT NULL COMMENT 'Age in years', + `salary` decimal(10,2) DEFAULT NULL COMMENT 'Annual salary in USD', + `is_active` tinyint(1) DEFAULT NULL COMMENT 'Whether the user account is active', + `created_at` timestamp NULL DEFAULT NULL COMMENT 'When the record was created' +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_field_comments; +CREATE TABLE t_table_comment ( +id INT, +data VARCHAR(50) +) COMMENT = 'This is a sample table for testing table-level comments'; +CREATE TABLE `t_table_comment` ( + `id` int(11) DEFAULT NULL, + `data` varchar(50) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci COMMENT='This is a sample table for testing table-level comments' +DROP TABLE t_table_comment; +CREATE TABLE t_mixed_comments ( +user_id INT COMMENT 'Unique identifier for each user', +username VARCHAR(50) COMMENT 'Login username (alphanumeric only)', +password_hash VARCHAR(255) COMMENT 'Hashed password using bcrypt', +email VARCHAR(255) COMMENT 'Primary email address', +phone VARCHAR(20) COMMENT 'Contact phone number', +status ENUM('active', 'suspended', 'deleted') COMMENT 'Current account status', +last_login DATETIME COMMENT 'Timestamp of last successful login', +failed_attempts INT COMMENT 'Number of consecutive failed login attempts', +created_by INT COMMENT 'User ID of the admin who created this account' +) COMMENT = 'User accounts table - stores authentication and basic user information'; +CREATE TABLE `t_mixed_comments` ( + `user_id` int(11) DEFAULT NULL COMMENT 'Unique identifier for each user', + `username` varchar(50) DEFAULT NULL COMMENT 'Login username (alphanumeric only)', + `password_hash` varchar(255) DEFAULT NULL COMMENT 'Hashed password using bcrypt', + `email` varchar(255) DEFAULT NULL COMMENT 'Primary email address', + `phone` varchar(20) DEFAULT NULL COMMENT 'Contact phone number', + `status` enum('active','suspended','deleted') DEFAULT NULL COMMENT 'Current account status', + `last_login` datetime DEFAULT NULL COMMENT 'Timestamp of last successful login', + `failed_attempts` int(11) DEFAULT NULL COMMENT 'Number of consecutive failed login attempts', + `created_by` int(11) DEFAULT NULL COMMENT 'User ID of the admin who created this account' +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci COMMENT='User accounts table - stores authentication and basic user information' +DROP TABLE t_mixed_comments; +CREATE TABLE t_special_char_comments ( +id INT COMMENT 'ID with "quotes" and \'apostrophes\'', +data TEXT COMMENT 'Data field with newlines\nand tabs\tand backslashes\\', +json_field JSON COMMENT 'JSON data: {"key": "value", "number": 123}', +unicode_field VARCHAR(100) COMMENT 'Unicode: café, naïve, résumé, 你好', +emoji_field VARCHAR(200) COMMENT 'Emoji support: 😀 🎉 👍 ⭐ 🔥' +) COMMENT = 'Table with special characters in comments: "quotes", \'apostrophes\', and 中文'; +CREATE TABLE `t_special_char_comments` ( + `id` int(11) DEFAULT NULL COMMENT 'ID with "quotes" and ''apostrophes''', + `data` text DEFAULT NULL COMMENT 'Data field with newlines\nand tabs and backslashes\\', + `json_field` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'JSON data: {"key": "value", "number": 123}' CHECK (json_valid(`json_field`)), + `unicode_field` varchar(100) DEFAULT NULL COMMENT 'Unicode: café, naïve, résumé, 你好', + `emoji_field` varchar(200) DEFAULT NULL COMMENT 'Emoji support: 😀 🎉 👍 ⭐ 🔥' +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci COMMENT='Table with special characters in comments: "quotes", ''apostrophes'', and 中文' +DROP TABLE t_special_char_comments; +CREATE TABLE t_long_comments ( +id INT COMMENT 'This is a very long comment that exceeds the typical short comment length to test how the FRM parser handles extended comment text that might span multiple lines or exceed certain buffer limits in the parsing logic', +description TEXT COMMENT 'Another long comment: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.', +notes VARCHAR(500) COMMENT 'Even longer comment with detailed explanation: This field is used to store additional notes about the record. It can contain up to 500 characters and should be used for storing metadata, processing instructions, or other supplementary information that doesn\'t fit in other fields. Please ensure proper validation before storing data here.' +) COMMENT = 'This table has exceptionally long comments to test the FRM parser\'s ability to handle extended comment text. The table is designed to store various types of textual data with comprehensive documentation for each field. This comment itself is quite lengthy to ensure proper parsing and display of table-level comments that exceed typical lengths.'; +CREATE TABLE `t_long_comments` ( + `id` int(11) DEFAULT NULL COMMENT 'This is a very long comment that exceeds the typical short comment length to test how the FRM parser handles extended comment text that might span multiple lines or exceed certain buffer limits in the parsing logic', + `description` text DEFAULT NULL COMMENT 'Another long comment: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.', + `notes` varchar(500) DEFAULT NULL COMMENT 'Even longer comment with detailed explanation: This field is used to store additional notes about the record. It can contain up to 500 characters and should be used for storing metadata, processing instructions, or other supplementary information that doesn''t fit in other fields. Please ensure proper validation before storing data here.' +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci COMMENT='This table has exceptionally long comments to test the FRM parser''s ability to handle extended comment text. The table is designed to store various types of textual data with comprehensive documentation for each field. This comment itself is quite lengthy to ensure proper parsing and display of table-level comments that exceed typical lengths.' +DROP TABLE t_long_comments; +CREATE TABLE t_empty_comments ( +id INT COMMENT '', +name VARCHAR(50) COMMENT '', +data TEXT +) COMMENT = ''; +CREATE TABLE `t_empty_comments` ( + `id` int(11) DEFAULT NULL, + `name` varchar(50) DEFAULT NULL, + `data` text DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_empty_comments; +Comments test completed diff --git a/mysql-test/suite/client/mariadb-frm-comments.test b/mysql-test/suite/client/mariadb-frm-comments.test new file mode 100644 index 0000000000000..14b051f986d18 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-comments.test @@ -0,0 +1,86 @@ +--echo MariaDB frm parser test - Comments +--echo Testing field-level and table-level comments + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} + + +# Test basic field comments +CREATE TABLE t_field_comments ( + id INT COMMENT 'Primary key identifier', + name VARCHAR(100) COMMENT 'Full name of the user', + email VARCHAR(255) COMMENT 'User email address (must be unique)', + age INT COMMENT 'Age in years', + salary DECIMAL(10,2) COMMENT 'Annual salary in USD', + is_active BOOLEAN COMMENT 'Whether the user account is active', + created_at TIMESTAMP COMMENT 'When the record was created' +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_field_comments.frm +DROP TABLE t_field_comments; + +# Test table-level comments +CREATE TABLE t_table_comment ( + id INT, + data VARCHAR(50) +) COMMENT = 'This is a sample table for testing table-level comments'; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_table_comment.frm +DROP TABLE t_table_comment; + +# Test both field and table comments +CREATE TABLE t_mixed_comments ( + user_id INT COMMENT 'Unique identifier for each user', + username VARCHAR(50) COMMENT 'Login username (alphanumeric only)', + password_hash VARCHAR(255) COMMENT 'Hashed password using bcrypt', + email VARCHAR(255) COMMENT 'Primary email address', + phone VARCHAR(20) COMMENT 'Contact phone number', + status ENUM('active', 'suspended', 'deleted') COMMENT 'Current account status', + last_login DATETIME COMMENT 'Timestamp of last successful login', + failed_attempts INT COMMENT 'Number of consecutive failed login attempts', + created_by INT COMMENT 'User ID of the admin who created this account' +) COMMENT = 'User accounts table - stores authentication and basic user information'; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_mixed_comments.frm +DROP TABLE t_mixed_comments; + +# Test comments with special characters +CREATE TABLE t_special_char_comments ( + id INT COMMENT 'ID with "quotes" and \'apostrophes\'', + data TEXT COMMENT 'Data field with newlines\nand tabs\tand backslashes\\', + json_field JSON COMMENT 'JSON data: {"key": "value", "number": 123}', + unicode_field VARCHAR(100) COMMENT 'Unicode: café, naïve, résumé, 你好', + emoji_field VARCHAR(200) COMMENT 'Emoji support: 😀 🎉 👍 ⭐ 🔥' +) COMMENT = 'Table with special characters in comments: "quotes", \'apostrophes\', and 中文'; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_special_char_comments.frm +DROP TABLE t_special_char_comments; + +# Test long comments +CREATE TABLE t_long_comments ( + id INT COMMENT 'This is a very long comment that exceeds the typical short comment length to test how the FRM parser handles extended comment text that might span multiple lines or exceed certain buffer limits in the parsing logic', + description TEXT COMMENT 'Another long comment: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.', + notes VARCHAR(500) COMMENT 'Even longer comment with detailed explanation: This field is used to store additional notes about the record. It can contain up to 500 characters and should be used for storing metadata, processing instructions, or other supplementary information that doesn\'t fit in other fields. Please ensure proper validation before storing data here.' +) COMMENT = 'This table has exceptionally long comments to test the FRM parser\'s ability to handle extended comment text. The table is designed to store various types of textual data with comprehensive documentation for each field. This comment itself is quite lengthy to ensure proper parsing and display of table-level comments that exceed typical lengths.'; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_long_comments.frm +DROP TABLE t_long_comments; + +# Test empty comments +CREATE TABLE t_empty_comments ( + id INT COMMENT '', + name VARCHAR(50) COMMENT '', + data TEXT +) COMMENT = ''; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_empty_comments.frm +DROP TABLE t_empty_comments; + + + +--echo Comments test completed diff --git a/mysql-test/suite/client/mariadb-frm-datatypes.result b/mysql-test/suite/client/mariadb-frm-datatypes.result new file mode 100644 index 0000000000000..4116a28a951a7 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-datatypes.result @@ -0,0 +1,134 @@ +MariaDB frm parser test - Data Types +Testing various column data types +CREATE TABLE t_integers ( +tiny_col TINYINT, +small_col SMALLINT, +medium_col MEDIUMINT, +int_col INT, +big_col BIGINT, +tiny_unsigned TINYINT UNSIGNED, +int_signed INT SIGNED +); +CREATE TABLE `t_integers` ( + `tiny_col` tinyint(4) DEFAULT NULL, + `small_col` smallint(6) DEFAULT NULL, + `medium_col` mediumint(9) DEFAULT NULL, + `int_col` int(11) DEFAULT NULL, + `big_col` bigint(20) DEFAULT NULL, + `tiny_unsigned` tinyint(3) unsigned DEFAULT NULL, + `int_signed` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_integers; +CREATE TABLE t_float ( +float_col FLOAT, +double_col DOUBLE, +real_col REAL, +float_precision FLOAT(7,4), +double_precision DOUBLE(15,8) +); +CREATE TABLE `t_float` ( + `float_col` float DEFAULT NULL, + `double_col` double DEFAULT NULL, + `real_col` double DEFAULT NULL, + `float_precision` float(7,4) DEFAULT NULL, + `double_precision` double(15,8) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_float; +CREATE TABLE t_decimal ( +dec_col DECIMAL, +dec_precision DECIMAL(10,2), +numeric_col NUMERIC(8,3), +fixed_col FIXED(12,4) +); +CREATE TABLE `t_decimal` ( + `dec_col` decimal(10,0) DEFAULT NULL, + `dec_precision` decimal(10,2) DEFAULT NULL, + `numeric_col` decimal(8,3) DEFAULT NULL, + `fixed_col` decimal(12,4) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_decimal; +CREATE TABLE t_strings ( +char_col CHAR(10), +varchar_col VARCHAR(255), +binary_col BINARY(16), +varbinary_col VARBINARY(100), +tinytext_col TINYTEXT, +text_col TEXT, +mediumtext_col MEDIUMTEXT, +longtext_col LONGTEXT +); +CREATE TABLE `t_strings` ( + `char_col` char(10) DEFAULT NULL, + `varchar_col` varchar(255) DEFAULT NULL, + `binary_col` binary(16) DEFAULT NULL, + `varbinary_col` varbinary(100) DEFAULT NULL, + `tinytext_col` tinytext DEFAULT NULL, + `text_col` text DEFAULT NULL, + `mediumtext_col` mediumtext DEFAULT NULL, + `longtext_col` longtext DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_strings; +CREATE TABLE t_blobs ( +tinyblob_col TINYBLOB, +blob_col BLOB, +mediumblob_col MEDIUMBLOB, +longblob_col LONGBLOB +); +CREATE TABLE `t_blobs` ( + `tinyblob_col` tinyblob DEFAULT NULL, + `blob_col` blob DEFAULT NULL, + `mediumblob_col` mediumblob DEFAULT NULL, + `longblob_col` longblob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_blobs; +CREATE TABLE t_datetime ( +date_col DATE, +time_col TIME, +datetime_col DATETIME, +timestamp_col TIMESTAMP, +year_col YEAR, +time_precision TIME(3), +datetime_precision DATETIME(6), +timestamp_precision TIMESTAMP(6) +); +CREATE TABLE `t_datetime` ( + `date_col` date DEFAULT NULL, + `time_col` time DEFAULT NULL, + `datetime_col` datetime DEFAULT NULL, + `timestamp_col` timestamp NULL DEFAULT NULL, + `year_col` year(4) DEFAULT NULL, + `time_precision` time(3) DEFAULT NULL, + `datetime_precision` datetime(6) DEFAULT NULL, + `timestamp_precision` timestamp(6) NULL DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_datetime; +CREATE TABLE t_enum_set ( +status ENUM('active', 'inactive', 'pending'), +permissions SET('read', 'write', 'execute', 'admin') +); +CREATE TABLE `t_enum_set` ( + `status` enum('active','inactive','pending') DEFAULT NULL, + `permissions` set('read','write','execute','admin') DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_enum_set; +CREATE TABLE t_bit ( +flag_col BIT, +bits_col BIT(8), +large_bits BIT(64) +); +CREATE TABLE `t_bit` ( + `flag_col` bit(1) DEFAULT NULL, + `bits_col` bit(8) DEFAULT NULL, + `large_bits` bit(64) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_bit; +CREATE TABLE t_json ( +id INT, +data JSON +); +CREATE TABLE `t_json` ( + `id` int(11) DEFAULT NULL, + `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`data`)) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_json; +Data types test completed diff --git a/mysql-test/suite/client/mariadb-frm-datatypes.test b/mysql-test/suite/client/mariadb-frm-datatypes.test new file mode 100644 index 0000000000000..a7a3d83dcbe55 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-datatypes.test @@ -0,0 +1,115 @@ +--echo MariaDB frm parser test - Data Types +--echo Testing various column data types + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} + +# Test basic integer types +CREATE TABLE t_integers ( + tiny_col TINYINT, + small_col SMALLINT, + medium_col MEDIUMINT, + int_col INT, + big_col BIGINT, + tiny_unsigned TINYINT UNSIGNED, + int_signed INT SIGNED +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_integers.frm +DROP TABLE t_integers; + +# Test floating point types +CREATE TABLE t_float ( + float_col FLOAT, + double_col DOUBLE, + real_col REAL, + float_precision FLOAT(7,4), + double_precision DOUBLE(15,8) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_float.frm +DROP TABLE t_float; + +# Test decimal/numeric types +CREATE TABLE t_decimal ( + dec_col DECIMAL, + dec_precision DECIMAL(10,2), + numeric_col NUMERIC(8,3), + fixed_col FIXED(12,4) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_decimal.frm +DROP TABLE t_decimal; + +# Test string types +CREATE TABLE t_strings ( + char_col CHAR(10), + varchar_col VARCHAR(255), + binary_col BINARY(16), + varbinary_col VARBINARY(100), + tinytext_col TINYTEXT, + text_col TEXT, + mediumtext_col MEDIUMTEXT, + longtext_col LONGTEXT +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_strings.frm +DROP TABLE t_strings; + +# Test blob types +CREATE TABLE t_blobs ( + tinyblob_col TINYBLOB, + blob_col BLOB, + mediumblob_col MEDIUMBLOB, + longblob_col LONGBLOB +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_blobs.frm +DROP TABLE t_blobs; + +# Test date and time types +CREATE TABLE t_datetime ( + date_col DATE, + time_col TIME, + datetime_col DATETIME, + timestamp_col TIMESTAMP, + year_col YEAR, + time_precision TIME(3), + datetime_precision DATETIME(6), + timestamp_precision TIMESTAMP(6) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_datetime.frm +DROP TABLE t_datetime; + +# Test enum and set types +CREATE TABLE t_enum_set ( + status ENUM('active', 'inactive', 'pending'), + permissions SET('read', 'write', 'execute', 'admin') +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_enum_set.frm +DROP TABLE t_enum_set; + +# Test bit type +CREATE TABLE t_bit ( + flag_col BIT, + bits_col BIT(8), + large_bits BIT(64) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_bit.frm +DROP TABLE t_bit; + +# Test JSON type (if available) + CREATE TABLE t_json ( + id INT, + data JSON + ); + --exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_json.frm +DROP TABLE t_json; + +--echo Data types test completed diff --git a/mysql-test/suite/client/mariadb-frm-defaults.result b/mysql-test/suite/client/mariadb-frm-defaults.result new file mode 100644 index 0000000000000..3815d7a0419d0 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-defaults.result @@ -0,0 +1,155 @@ +MariaDB frm parser test - Default Values +Testing various DEFAULT expressions and values +CREATE TABLE t_basic_defaults ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(50) DEFAULT 'Anonymous', +status VARCHAR(20) DEFAULT 'pending', +priority INT DEFAULT 1, +score DECIMAL(5,2) DEFAULT 0.00, +is_active BOOLEAN DEFAULT TRUE, +is_deleted BOOLEAN DEFAULT FALSE +); +CREATE TABLE `t_basic_defaults` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(50) DEFAULT 'Anonymous', + `status` varchar(20) DEFAULT 'pending', + `priority` int(11) DEFAULT 1, + `score` decimal(5,2) DEFAULT 0.00, + `is_active` tinyint(1) DEFAULT 1, + `is_deleted` tinyint(1) DEFAULT 0, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_basic_defaults; +CREATE TABLE t_numeric_defaults ( +tiny_val TINYINT DEFAULT 127, +small_val SMALLINT DEFAULT -32768, +int_val INT DEFAULT 2147483647, +big_val BIGINT DEFAULT 9223372036854775807, +float_val FLOAT DEFAULT 3.14159, +double_val DOUBLE DEFAULT 2.718281828, +decimal_val DECIMAL(10,3) DEFAULT 999.999 +); +CREATE TABLE `t_numeric_defaults` ( + `tiny_val` tinyint(4) DEFAULT 127, + `small_val` smallint(6) DEFAULT -32768, + `int_val` int(11) DEFAULT 2147483647, + `big_val` bigint(20) DEFAULT 9223372036854775807, + `float_val` float DEFAULT 3.14159, + `double_val` double DEFAULT 2.718281828, + `decimal_val` decimal(10,3) DEFAULT 999.999 +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_numeric_defaults; +CREATE TABLE t_timestamp_defaults ( +id INT AUTO_INCREMENT PRIMARY KEY, +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +modified_time TIMESTAMP DEFAULT NOW(), +current_date_col DATE DEFAULT CURRENT_DATE, +current_time_col TIME DEFAULT CURRENT_TIME +); +CREATE TABLE `t_timestamp_defaults` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT current_timestamp(0), + `updated_at` timestamp NULL DEFAULT current_timestamp(0) ON UPDATE current_timestamp(), + `modified_time` timestamp NULL DEFAULT current_timestamp(0), + `current_date_col` date DEFAULT curdate(), + `current_time_col` time DEFAULT curtime(), + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_timestamp_defaults; +CREATE TABLE t_expression_defaults ( +id INT, +calculated INT DEFAULT (1 + 2 * 3), +random_val INT DEFAULT (FLOOR(RAND() * 100)), +uuid_val VARCHAR(36) DEFAULT (UUID()), +connection_id_val INT DEFAULT (CONNECTION_ID()), +math_result DECIMAL(10,4) DEFAULT (PI() * 2) +); +Warnings: +Note 1265 Data truncated for column 'math_result' at row 0 +CREATE TABLE `t_expression_defaults` ( + `id` int(11) DEFAULT NULL, + `calculated` int(11) DEFAULT 1 + 2 * 3, + `random_val` int(11) DEFAULT floor(rand() * 100), + `uuid_val` varchar(36) DEFAULT uuid(), + `connection_id_val` int(11) DEFAULT connection_id(), + `math_result` decimal(10,4) DEFAULT pi() * 2 +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_expression_defaults; +CREATE TABLE t_string_defaults ( +simple_text VARCHAR(50) DEFAULT 'Hello World', +quoted_text VARCHAR(100) DEFAULT 'It''s a "quoted" string', +empty_string VARCHAR(10) DEFAULT '', +null_default VARCHAR(50) DEFAULT NULL, +json_default TEXT DEFAULT '{"status": "new", "count": 0}', +multiline_default TEXT DEFAULT 'Line 1\nLine 2\nLine 3' +); +CREATE TABLE `t_string_defaults` ( + `simple_text` varchar(50) DEFAULT 'Hello World', + `quoted_text` varchar(100) DEFAULT 'It''s a "quoted" string', + `empty_string` varchar(10) DEFAULT '', + `null_default` varchar(50) DEFAULT NULL, + `json_default` text DEFAULT '{"status": "new", "count": 0}', + `multiline_default` text DEFAULT 'Line 1\nLine 2\nLine 3' +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_string_defaults; +CREATE TABLE t_enum_set_defaults ( +status ENUM('draft', 'published', 'archived') DEFAULT 'draft', +permissions SET('read', 'write', 'delete', 'admin') DEFAULT 'read', +priority ENUM('low', 'medium', 'high') DEFAULT 'medium', +flags SET('flag1', 'flag2', 'flag3') DEFAULT 'flag1,flag2' +); +CREATE TABLE `t_enum_set_defaults` ( + `status` enum('draft','published','archived') DEFAULT 'draft', + `permissions` set('read','write','delete','admin') DEFAULT 'read', + `priority` enum('low','medium','high') DEFAULT 'medium', + `flags` set('flag1','flag2','flag3') DEFAULT 'flag1,flag2' +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_enum_set_defaults; +CREATE TABLE t_datetime_defaults ( +birth_date DATE DEFAULT '1970-01-01', +appointment_time TIME DEFAULT '09:00:00', +event_datetime DATETIME DEFAULT '2024-01-01 00:00:00', +year_val YEAR DEFAULT 2024, +created_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +zero_date DATE DEFAULT '0000-00-00', +zero_datetime DATETIME DEFAULT '0000-00-00 00:00:00' +); +CREATE TABLE `t_datetime_defaults` ( + `birth_date` date DEFAULT '1970-01-01', + `appointment_time` time DEFAULT '09:00:00', + `event_datetime` datetime DEFAULT '2024-01-01 00:00:00', + `year_val` year(4) DEFAULT 2024, + `created_ts` timestamp NULL DEFAULT current_timestamp(0), + `zero_date` date DEFAULT '0000-00-00', + `zero_datetime` datetime DEFAULT '0000-00-00 00:00:00' +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_datetime_defaults; +CREATE TABLE t_auto_increment_defaults ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(50) DEFAULT 'Item' +) AUTO_INCREMENT = 1000; +CREATE TABLE `t_auto_increment_defaults` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(50) DEFAULT 'Item', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_auto_increment_defaults; +CREATE TABLE t_complex_defaults ( +id INT, +hash_val VARCHAR(64) DEFAULT (SHA2(RAND(), 256)), +formatted_date VARCHAR(20) DEFAULT (DATE_FORMAT(NOW(), '%Y-%m-%d')), +case_result VARCHAR(10) DEFAULT (CASE WHEN RAND() > 0.5 THEN 'HIGH' ELSE 'LOW' END), +conditional_val INT DEFAULT (IF(RAND() > 0.5, 100, 0)), +substr_result VARCHAR(10) DEFAULT (SUBSTRING('HelloWorld', 1, 5)) +); +CREATE TABLE `t_complex_defaults` ( + `id` int(11) DEFAULT NULL, + `hash_val` varchar(64) DEFAULT sha2(rand(),256), + `formatted_date` varchar(20) DEFAULT date_format(current_timestamp(),'%Y-%m-%d'), + `case_result` varchar(10) DEFAULT case when rand() > 0.5 then 'HIGH' else 'LOW' end, + `conditional_val` int(11) DEFAULT if(rand() > 0.5,100,0), + `substr_result` varchar(10) DEFAULT substr('HelloWorld',1,5) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_complex_defaults; +Default values test completed diff --git a/mysql-test/suite/client/mariadb-frm-defaults.test b/mysql-test/suite/client/mariadb-frm-defaults.test new file mode 100644 index 0000000000000..dc50982aff342 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-defaults.test @@ -0,0 +1,123 @@ +--echo MariaDB frm parser test - Default Values +--echo Testing various DEFAULT expressions and values + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} + +# Test basic default values +CREATE TABLE t_basic_defaults ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) DEFAULT 'Anonymous', + status VARCHAR(20) DEFAULT 'pending', + priority INT DEFAULT 1, + score DECIMAL(5,2) DEFAULT 0.00, + is_active BOOLEAN DEFAULT TRUE, + is_deleted BOOLEAN DEFAULT FALSE +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_basic_defaults.frm +DROP TABLE t_basic_defaults; + +# Test numeric defaults +CREATE TABLE t_numeric_defaults ( + tiny_val TINYINT DEFAULT 127, + small_val SMALLINT DEFAULT -32768, + int_val INT DEFAULT 2147483647, + big_val BIGINT DEFAULT 9223372036854775807, + float_val FLOAT DEFAULT 3.14159, + double_val DOUBLE DEFAULT 2.718281828, + decimal_val DECIMAL(10,3) DEFAULT 999.999 +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_numeric_defaults.frm +DROP TABLE t_numeric_defaults; + +# Test timestamp defaults (special case) +CREATE TABLE t_timestamp_defaults ( + id INT AUTO_INCREMENT PRIMARY KEY, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + modified_time TIMESTAMP DEFAULT NOW(), + current_date_col DATE DEFAULT CURRENT_DATE, + current_time_col TIME DEFAULT CURRENT_TIME +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_timestamp_defaults.frm +DROP TABLE t_timestamp_defaults; + +# Test expression defaults +CREATE TABLE t_expression_defaults ( + id INT, + calculated INT DEFAULT (1 + 2 * 3), + random_val INT DEFAULT (FLOOR(RAND() * 100)), + uuid_val VARCHAR(36) DEFAULT (UUID()), + connection_id_val INT DEFAULT (CONNECTION_ID()), + math_result DECIMAL(10,4) DEFAULT (PI() * 2) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_expression_defaults.frm +DROP TABLE t_expression_defaults; + +# Test string defaults with special characters +CREATE TABLE t_string_defaults ( + simple_text VARCHAR(50) DEFAULT 'Hello World', + quoted_text VARCHAR(100) DEFAULT 'It''s a "quoted" string', + empty_string VARCHAR(10) DEFAULT '', + null_default VARCHAR(50) DEFAULT NULL, + json_default TEXT DEFAULT '{"status": "new", "count": 0}', + multiline_default TEXT DEFAULT 'Line 1\nLine 2\nLine 3' +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_string_defaults.frm +DROP TABLE t_string_defaults; + +# Test enum and set defaults +CREATE TABLE t_enum_set_defaults ( + status ENUM('draft', 'published', 'archived') DEFAULT 'draft', + permissions SET('read', 'write', 'delete', 'admin') DEFAULT 'read', + priority ENUM('low', 'medium', 'high') DEFAULT 'medium', + flags SET('flag1', 'flag2', 'flag3') DEFAULT 'flag1,flag2' +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_enum_set_defaults.frm +DROP TABLE t_enum_set_defaults; + +# Test date/time defaults +CREATE TABLE t_datetime_defaults ( + birth_date DATE DEFAULT '1970-01-01', + appointment_time TIME DEFAULT '09:00:00', + event_datetime DATETIME DEFAULT '2024-01-01 00:00:00', + year_val YEAR DEFAULT 2024, + created_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + zero_date DATE DEFAULT '0000-00-00', + zero_datetime DATETIME DEFAULT '0000-00-00 00:00:00' +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_datetime_defaults.frm +DROP TABLE t_datetime_defaults; + +# Test auto increment with specific start value +CREATE TABLE t_auto_increment_defaults ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) DEFAULT 'Item' +) AUTO_INCREMENT = 1000; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_auto_increment_defaults.frm +DROP TABLE t_auto_increment_defaults; + +# Test complex expression defaults +CREATE TABLE t_complex_defaults ( + id INT, + hash_val VARCHAR(64) DEFAULT (SHA2(RAND(), 256)), + formatted_date VARCHAR(20) DEFAULT (DATE_FORMAT(NOW(), '%Y-%m-%d')), + case_result VARCHAR(10) DEFAULT (CASE WHEN RAND() > 0.5 THEN 'HIGH' ELSE 'LOW' END), + conditional_val INT DEFAULT (IF(RAND() > 0.5, 100, 0)), + substr_result VARCHAR(10) DEFAULT (SUBSTRING('HelloWorld', 1, 5)) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_complex_defaults.frm +DROP TABLE t_complex_defaults; + +--echo Default values test completed diff --git a/mysql-test/suite/client/mariadb-frm-engines.result b/mysql-test/suite/client/mariadb-frm-engines.result new file mode 100644 index 0000000000000..53c91709635bd --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-engines.result @@ -0,0 +1,172 @@ +MariaDB frm parser test - Storage Engines +Testing various storage engines +CREATE TABLE t_myisam ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(100), +data TEXT, +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +INDEX idx_name (name) +) ENGINE=MyISAM; +CREATE TABLE `t_myisam` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `data` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0), + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_myisam; +CREATE TABLE t_innodb ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(100), +email VARCHAR(255), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +UNIQUE KEY idx_email (email), +INDEX idx_name (name) +) ENGINE=InnoDB; +CREATE TABLE `t_innodb` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `email` varchar(255) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0), + PRIMARY KEY (`id`), + UNIQUE KEY `idx_email` (`email`), + KEY `idx_name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_innodb; +CREATE TABLE t_aria ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(100), +description TEXT, +status ENUM('active', 'inactive') DEFAULT 'active', +score DECIMAL(10,2), +INDEX idx_name (name), +INDEX idx_status (status) +) ENGINE=Aria; +CREATE TABLE `t_aria` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `description` text DEFAULT NULL, + `status` enum('active','inactive') DEFAULT 'active', + `score` decimal(10,2) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_name` (`name`), + KEY `idx_status` (`status`) +) ENGINE=Aria DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_aria; +CREATE TABLE t_memory ( +id INT PRIMARY KEY, +session_id VARCHAR(32), +data VARCHAR(255), +expires_at TIMESTAMP, +INDEX idx_session (session_id) USING HASH, +INDEX idx_expires (expires_at) USING BTREE +) ENGINE=MEMORY; +CREATE TABLE `t_memory` ( + `id` int(11) NOT NULL, + `session_id` varchar(32) DEFAULT NULL, + `data` varchar(255) DEFAULT NULL, + `expires_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_session` (`session_id`) USING HASH, + KEY `idx_expires` (`expires_at`) USING BTREE +) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_memory; +CREATE TABLE t_csv ( +id INT NOT NULL, +name VARCHAR(100) NOT NULL, +email VARCHAR(255) NOT NULL, +phone VARCHAR(20) NOT NULL, +created_date DATE NOT NULL +) ENGINE=CSV; +CREATE TABLE `t_csv` ( + `id` int(11) NOT NULL, + `name` varchar(100) NOT NULL, + `email` varchar(255) NOT NULL, + `phone` varchar(20) NOT NULL, + `created_date` date NOT NULL +) ENGINE=CSV DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_csv; +/Users/himanshu.pandey/codes/gh-repos/my-maria-server/mysql-test/std_data/frm/t_archive.frm +CREATE TABLE `t_archive` ( + `id` int(11) DEFAULT NULL, + `log_message` text DEFAULT NULL, + `log_level` varchar(10) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0) +) ENGINE=ARCHIVE DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +CREATE TABLE t_blackhole ( +id INT AUTO_INCREMENT PRIMARY KEY, +data VARCHAR(255), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=BLACKHOLE; +CREATE TABLE `t_blackhole` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `data` varchar(255) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0), + PRIMARY KEY (`id`) +) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_blackhole; +CREATE TABLE t_innodb_options ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(100), +data JSON, +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB +ROW_FORMAT=DYNAMIC; +CREATE TABLE `t_innodb_options` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`data`)), + `created_at` timestamp NULL DEFAULT current_timestamp(0), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci ROW_FORMAT=DYNAMIC +DROP TABLE t_innodb_options; +CREATE TABLE t_myisam_options ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(100), +data TEXT, +INDEX idx_name (name) +) ENGINE=MyISAM +MAX_ROWS=1000000 +AVG_ROW_LENGTH=100 +PACK_KEYS=1; +CREATE TABLE `t_myisam_options` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `data` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci MAX_ROWS=1000000 AVG_ROW_LENGTH=100 PACK_KEYS=1 +DROP TABLE t_myisam_options; +CREATE TABLE t_aria_options ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(100), +data TEXT, +INDEX idx_name (name) +) ENGINE=Aria +ROW_FORMAT=PAGE +TRANSACTIONAL=1 +PAGE_CHECKSUM=1; +CREATE TABLE `t_aria_options` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `data` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) +) ENGINE=Aria DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci PAGE_CHECKSUM=1 ROW_FORMAT=PAGE /* TRANSACTIONAL=1 */ +DROP TABLE t_aria_options; +CREATE TABLE t_memory_options ( +id INT PRIMARY KEY, +data VARCHAR(255), +INDEX idx_data (data) USING HASH +) ENGINE=MEMORY +MAX_ROWS=10000 +AVG_ROW_LENGTH=50; +CREATE TABLE `t_memory_options` ( + `id` int(11) NOT NULL, + `data` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_data` (`data`) USING HASH +) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci MAX_ROWS=10000 AVG_ROW_LENGTH=50 +DROP TABLE t_memory_options; +Storage engines test completed diff --git a/mysql-test/suite/client/mariadb-frm-engines.test b/mysql-test/suite/client/mariadb-frm-engines.test new file mode 100644 index 0000000000000..2b418ef2f1a88 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-engines.test @@ -0,0 +1,170 @@ +--echo MariaDB frm parser test - Storage Engines +--echo Testing various storage engines + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} +CREATE TABLE t_myisam ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + data TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_name (name) +) ENGINE=MyISAM; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_myisam.frm +DROP TABLE t_myisam; + +# Test InnoDB engine +--source include/have_innodb.inc +CREATE TABLE t_innodb ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY idx_email (email), + INDEX idx_name (name) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_innodb.frm +DROP TABLE t_innodb; + +# Test Aria engine (MariaDB's enhanced MyISAM) +--source include/have_aria.inc +CREATE TABLE t_aria ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + description TEXT, + status ENUM('active', 'inactive') DEFAULT 'active', + score DECIMAL(10,2), + INDEX idx_name (name), + INDEX idx_status (status) +) ENGINE=Aria; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_aria.frm +DROP TABLE t_aria; + +# Test MEMORY engine (HEAP) +CREATE TABLE t_memory ( + id INT PRIMARY KEY, + session_id VARCHAR(32), + data VARCHAR(255), + expires_at TIMESTAMP, + INDEX idx_session (session_id) USING HASH, + INDEX idx_expires (expires_at) USING BTREE +) ENGINE=MEMORY; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_memory.frm +DROP TABLE t_memory; + +# Test CSV engine +CREATE TABLE t_csv ( + id INT NOT NULL, + name VARCHAR(100) NOT NULL, + email VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL, + created_date DATE NOT NULL +) ENGINE=CSV; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_csv.frm +DROP TABLE t_csv; + +#Test ARCHIVE engine +--source include/have_archive.inc +--let $frm_file= $MYSQL_TEST_DIR/std_data/frm/t_archive.frm +--echo $frm_file +--file_exists $frm_file +--exec $MARIADB_FRM $frm_file + +# Test BLACKHOLE engine +--source include/have_blackhole.inc +CREATE TABLE t_blackhole ( + id INT AUTO_INCREMENT PRIMARY KEY, + data VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=BLACKHOLE; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_blackhole.frm +DROP TABLE t_blackhole; + +# Test FEDERATED engine (if available) +if (`SELECT COUNT(*) FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME IN ('FEDERATED','FEDERATEDX') AND PLUGIN_STATUS='ACTIVE'`) +{ + # Create a source table first + CREATE TABLE t_federated_source ( + id INT PRIMARY KEY, + name VARCHAR(100), + data TEXT + ) ENGINE=InnoDB; + + # Create FEDERATED table that connects to the source table + --replace_result $MASTER_MYPORT MASTER_PORT + eval CREATE TABLE t_federated ( + id INT PRIMARY KEY, + name VARCHAR(100), + data TEXT + ) ENGINE=FEDERATED CONNECTION='mysql://root@127.0.0.1:$MASTER_MYPORT/test/t_federated_source'; + + # Test the FRM parsing + --exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_federated.frm + + # Cleanup + DROP TABLE t_federated; + DROP TABLE t_federated_source; +} + +# Test engine-specific options for InnoDB +--source include/have_innodb.inc +CREATE TABLE t_innodb_options ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + data JSON, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB + ROW_FORMAT=DYNAMIC; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_innodb_options.frm +DROP TABLE t_innodb_options; + +# Test engine-specific options for MyISAM +CREATE TABLE t_myisam_options ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + data TEXT, + INDEX idx_name (name) +) ENGINE=MyISAM + MAX_ROWS=1000000 + AVG_ROW_LENGTH=100 + PACK_KEYS=1; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_myisam_options.frm +DROP TABLE t_myisam_options; + +# Test engine-specific options for Aria +--source include/have_aria.inc +CREATE TABLE t_aria_options ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + data TEXT, + INDEX idx_name (name) +) ENGINE=Aria + ROW_FORMAT=PAGE + TRANSACTIONAL=1 + PAGE_CHECKSUM=1; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_aria_options.frm +DROP TABLE t_aria_options; + +# Test MEMORY engine with specific options +CREATE TABLE t_memory_options ( + id INT PRIMARY KEY, + data VARCHAR(255), + INDEX idx_data (data) USING HASH +) ENGINE=MEMORY + MAX_ROWS=10000 + AVG_ROW_LENGTH=50; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_memory_options.frm +DROP TABLE t_memory_options; + +--echo Storage engines test completed diff --git a/mysql-test/suite/client/mariadb-frm-foreign-keys.result b/mysql-test/suite/client/mariadb-frm-foreign-keys.result new file mode 100644 index 0000000000000..b195017e2725c --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-foreign-keys.result @@ -0,0 +1,388 @@ +MariaDB frm parser test - Foreign Key Constraints +Testing foreign key relationships and constraint metadata parsing +CREATE TABLE t_parent_basic ( +id INT PRIMARY KEY, +name VARCHAR(100) NOT NULL, +category VARCHAR(50) DEFAULT 'general', +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB; +CREATE TABLE t_child_basic ( +id INT AUTO_INCREMENT PRIMARY KEY, +parent_id INT NOT NULL, +description TEXT, +status ENUM('active', 'inactive', 'pending') DEFAULT 'pending', +FOREIGN KEY fk_parent_basic (parent_id) REFERENCES t_parent_basic(id) +) ENGINE=InnoDB; +CREATE TABLE `t_child_basic` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `parent_id` int(11) NOT NULL, + `description` text DEFAULT NULL, + `status` enum('active','inactive','pending') DEFAULT 'pending', + PRIMARY KEY (`id`), + KEY `fk_parent_basic` (`parent_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_child_basic; +DROP TABLE t_parent_basic; +CREATE TABLE t_users ( +user_id INT PRIMARY KEY, +username VARCHAR(50) UNIQUE NOT NULL, +email VARCHAR(255) UNIQUE, +department_id INT, +manager_id INT, +created_date DATE DEFAULT CURRENT_DATE +) ENGINE=InnoDB; +CREATE TABLE t_posts ( +post_id INT AUTO_INCREMENT PRIMARY KEY, +title VARCHAR(200) NOT NULL, +content LONGTEXT, +author_id INT NOT NULL, +category_id INT, +published_at DATETIME, +# Different foreign key actions +CONSTRAINT fk_posts_author +FOREIGN KEY (author_id) REFERENCES t_users(user_id) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +CREATE TABLE `t_posts` ( + `post_id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(200) NOT NULL, + `content` longtext DEFAULT NULL, + `author_id` int(11) NOT NULL, + `category_id` int(11) DEFAULT NULL, + `published_at` datetime DEFAULT NULL, + PRIMARY KEY (`post_id`), + KEY `fk_posts_author` (`author_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_posts; +DROP TABLE t_users; +CREATE TABLE t_companies ( +company_code VARCHAR(10), +branch_code VARCHAR(5), +company_name VARCHAR(100) NOT NULL, +address TEXT, +PRIMARY KEY (company_code, branch_code) +) ENGINE=InnoDB; +CREATE TABLE t_employees ( +emp_id INT AUTO_INCREMENT PRIMARY KEY, +emp_number VARCHAR(20) UNIQUE, +first_name VARCHAR(50) NOT NULL, +last_name VARCHAR(50) NOT NULL, +company_code VARCHAR(10), +branch_code VARCHAR(5), +department VARCHAR(50), +salary DECIMAL(15,2) DEFAULT 0.00, +hire_date DATE, +# Multi-column foreign key +CONSTRAINT fk_emp_company_branch +FOREIGN KEY (company_code, branch_code) +REFERENCES t_companies(company_code, branch_code) +ON DELETE RESTRICT ON UPDATE CASCADE, +INDEX idx_company_branch (company_code, branch_code), +INDEX idx_emp_name (last_name, first_name) +) ENGINE=InnoDB; +CREATE TABLE `t_employees` ( + `emp_id` int(11) NOT NULL AUTO_INCREMENT, + `emp_number` varchar(20) DEFAULT NULL, + `first_name` varchar(50) NOT NULL, + `last_name` varchar(50) NOT NULL, + `company_code` varchar(10) DEFAULT NULL, + `branch_code` varchar(5) DEFAULT NULL, + `department` varchar(50) DEFAULT NULL, + `salary` decimal(15,2) DEFAULT 0.00, + `hire_date` date DEFAULT NULL, + PRIMARY KEY (`emp_id`), + UNIQUE KEY `emp_number` (`emp_number`), + KEY `idx_company_branch` (`company_code`,`branch_code`), + KEY `idx_emp_name` (`last_name`,`first_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_employees; +DROP TABLE t_companies; +CREATE TABLE t_categories ( +category_id INT AUTO_INCREMENT PRIMARY KEY, +category_name VARCHAR(100) NOT NULL, +parent_category_id INT, +level INT DEFAULT 1, +sort_order INT DEFAULT 0, +is_active BOOLEAN DEFAULT TRUE, +description TEXT, +# Self-referencing foreign key +CONSTRAINT fk_category_parent +FOREIGN KEY (parent_category_id) REFERENCES t_categories(category_id) +ON DELETE CASCADE ON UPDATE CASCADE, +INDEX idx_parent_category (parent_category_id), +INDEX idx_category_name (category_name), +INDEX idx_sort_order (sort_order) +) ENGINE=InnoDB; +CREATE TABLE `t_categories` ( + `category_id` int(11) NOT NULL AUTO_INCREMENT, + `category_name` varchar(100) NOT NULL, + `parent_category_id` int(11) DEFAULT NULL, + `level` int(11) DEFAULT 1, + `sort_order` int(11) DEFAULT 0, + `is_active` tinyint(1) DEFAULT 1, + `description` text DEFAULT NULL, + PRIMARY KEY (`category_id`), + KEY `idx_parent_category` (`parent_category_id`), + KEY `idx_category_name` (`category_name`), + KEY `idx_sort_order` (`sort_order`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_categories; +CREATE TABLE t_departments ( +dept_id INT PRIMARY KEY, +dept_name VARCHAR(100) UNIQUE NOT NULL, +budget DECIMAL(20,2) DEFAULT 0 +) ENGINE=InnoDB; +CREATE TABLE t_projects ( +project_id INT PRIMARY KEY, +project_name VARCHAR(100) UNIQUE NOT NULL, +start_date DATE, +end_date DATE, +budget DECIMAL(20,2) +) ENGINE=InnoDB; +CREATE TABLE t_staff ( +staff_id INT AUTO_INCREMENT PRIMARY KEY, +employee_number VARCHAR(20) UNIQUE, +first_name VARCHAR(50) NOT NULL, +last_name VARCHAR(50) NOT NULL, +email VARCHAR(100), +phone VARCHAR(20), +department_id INT, +project_id INT, +manager_id INT, +hire_date DATE, +salary DECIMAL(12,2), +# Multiple foreign keys with different actions +CONSTRAINT fk_staff_department +FOREIGN KEY (department_id) REFERENCES t_departments(dept_id) +ON DELETE SET NULL ON UPDATE CASCADE, +CONSTRAINT fk_staff_project +FOREIGN KEY (project_id) REFERENCES t_projects(project_id) +ON DELETE SET NULL ON UPDATE CASCADE, +CONSTRAINT fk_staff_manager +FOREIGN KEY (manager_id) REFERENCES t_staff(staff_id) +ON DELETE SET NULL ON UPDATE CASCADE, +INDEX idx_staff_dept (department_id), +INDEX idx_staff_project (project_id), +INDEX idx_staff_manager (manager_id), +INDEX idx_staff_name (last_name, first_name), +INDEX idx_staff_email (email) +) ENGINE=InnoDB; +CREATE TABLE `t_staff` ( + `staff_id` int(11) NOT NULL AUTO_INCREMENT, + `employee_number` varchar(20) DEFAULT NULL, + `first_name` varchar(50) NOT NULL, + `last_name` varchar(50) NOT NULL, + `email` varchar(100) DEFAULT NULL, + `phone` varchar(20) DEFAULT NULL, + `department_id` int(11) DEFAULT NULL, + `project_id` int(11) DEFAULT NULL, + `manager_id` int(11) DEFAULT NULL, + `hire_date` date DEFAULT NULL, + `salary` decimal(12,2) DEFAULT NULL, + PRIMARY KEY (`staff_id`), + UNIQUE KEY `employee_number` (`employee_number`), + KEY `idx_staff_dept` (`department_id`), + KEY `idx_staff_project` (`project_id`), + KEY `idx_staff_manager` (`manager_id`), + KEY `idx_staff_name` (`last_name`,`first_name`), + KEY `idx_staff_email` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_staff; +DROP TABLE t_projects; +DROP TABLE t_departments; +CREATE TABLE t_lookup_tables ( +table_id CHAR(5) PRIMARY KEY, +table_name VARCHAR(50) NOT NULL, +description TEXT +) ENGINE=InnoDB; +CREATE TABLE t_lookup_values ( +value_id INT AUTO_INCREMENT PRIMARY KEY, +table_id CHAR(5), +value_code VARCHAR(20), +value_name VARCHAR(100), +sort_order SMALLINT DEFAULT 0, +is_default BOOLEAN DEFAULT FALSE, +CONSTRAINT fk_lookup_table +FOREIGN KEY (table_id) REFERENCES t_lookup_tables(table_id) +ON DELETE CASCADE ON UPDATE CASCADE, +UNIQUE KEY uk_table_code (table_id, value_code), +INDEX idx_sort_order (sort_order) +) ENGINE=InnoDB; +CREATE TABLE `t_lookup_values` ( + `value_id` int(11) NOT NULL AUTO_INCREMENT, + `table_id` char(5) DEFAULT NULL, + `value_code` varchar(20) DEFAULT NULL, + `value_name` varchar(100) DEFAULT NULL, + `sort_order` smallint(6) DEFAULT 0, + `is_default` tinyint(1) DEFAULT 0, + PRIMARY KEY (`value_id`), + UNIQUE KEY `uk_table_code` (`table_id`,`value_code`), + KEY `idx_sort_order` (`sort_order`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_lookup_values; +DROP TABLE t_lookup_tables; +CREATE TABLE t_very_long_parent_table_name_for_testing_frm_parser_limits ( +reference_id BIGINT PRIMARY KEY, +reference_code VARCHAR(50) UNIQUE, +reference_description LONGTEXT, +created_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB; +CREATE TABLE t_very_long_child_table_name_for_testing_frm_parser_capability ( +child_record_id BIGINT AUTO_INCREMENT PRIMARY KEY, +parent_reference_id BIGINT NOT NULL, +child_specific_data JSON, +processing_status ENUM('new', 'processing', 'completed', 'failed') DEFAULT 'new', +last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +# Long constraint name +CONSTRAINT fk_child_table_parent_ref +FOREIGN KEY (parent_reference_id) +REFERENCES t_very_long_parent_table_name_for_testing_frm_parser_limits(reference_id) +ON DELETE RESTRICT ON UPDATE CASCADE, +INDEX idx_parent_ref (parent_reference_id), +INDEX idx_status (processing_status) +) ENGINE=InnoDB; +CREATE TABLE `t_very_long_child_table_name_for_testing_frm_parser_capability` ( + `child_record_id` bigint(20) NOT NULL AUTO_INCREMENT, + `parent_reference_id` bigint(20) NOT NULL, + `child_specific_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`child_specific_data`)), + `processing_status` enum('new','processing','completed','failed') DEFAULT 'new', + `last_modified` timestamp NULL DEFAULT current_timestamp(0) ON UPDATE current_timestamp(), + PRIMARY KEY (`child_record_id`), + KEY `idx_parent_ref` (`parent_reference_id`), + KEY `idx_status` (`processing_status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_very_long_child_table_name_for_testing_frm_parser_capability; +DROP TABLE t_very_long_parent_table_name_for_testing_frm_parser_limits; +CREATE TABLE t_master_entities ( +entity_uuid CHAR(36) PRIMARY KEY, +entity_type VARCHAR(50) NOT NULL, +entity_name VARCHAR(200) NOT NULL, +version_number INT DEFAULT 1, +is_active TINYINT(1) DEFAULT 1, +metadata JSON, +created_by VARCHAR(100), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +UNIQUE KEY uk_entity_type_name (entity_type, entity_name), +INDEX idx_entity_type (entity_type), +INDEX idx_active_entities (is_active, entity_type) +) ENGINE=InnoDB; +CREATE TABLE t_entity_relationships ( +relationship_id BIGINT AUTO_INCREMENT PRIMARY KEY, +source_entity_uuid CHAR(36) NOT NULL, +target_entity_uuid CHAR(36) NOT NULL, +relationship_type VARCHAR(50) NOT NULL, +relationship_strength DECIMAL(3,2) DEFAULT 1.00, +relationship_metadata JSON, +effective_from DATE, +effective_to DATE, +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +# Multiple foreign keys to same table +CONSTRAINT fk_relationship_source_entity +FOREIGN KEY (source_entity_uuid) +REFERENCES t_master_entities(entity_uuid) +ON DELETE CASCADE ON UPDATE CASCADE, +CONSTRAINT fk_relationship_target_entity +FOREIGN KEY (target_entity_uuid) +REFERENCES t_master_entities(entity_uuid) +ON DELETE CASCADE ON UPDATE CASCADE, +# Prevent self-referencing in application logic, but allow in DB +UNIQUE KEY uk_source_target_type (source_entity_uuid, target_entity_uuid, relationship_type), +INDEX idx_source_entity (source_entity_uuid), +INDEX idx_target_entity (target_entity_uuid), +INDEX idx_relationship_type (relationship_type), +INDEX idx_effective_period (effective_from, effective_to) +) ENGINE=InnoDB; +CREATE TABLE `t_entity_relationships` ( + `relationship_id` bigint(20) NOT NULL AUTO_INCREMENT, + `source_entity_uuid` char(36) NOT NULL, + `target_entity_uuid` char(36) NOT NULL, + `relationship_type` varchar(50) NOT NULL, + `relationship_strength` decimal(3,2) DEFAULT 1.00, + `relationship_metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`relationship_metadata`)), + `effective_from` date DEFAULT NULL, + `effective_to` date DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0), + PRIMARY KEY (`relationship_id`), + UNIQUE KEY `uk_source_target_type` (`source_entity_uuid`,`target_entity_uuid`,`relationship_type`), + KEY `idx_source_entity` (`source_entity_uuid`), + KEY `idx_target_entity` (`target_entity_uuid`), + KEY `idx_relationship_type` (`relationship_type`), + KEY `idx_effective_period` (`effective_from`,`effective_to`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_entity_relationships; +DROP TABLE t_master_entities; +CREATE TABLE t_audit_log ( +log_id BIGINT AUTO_INCREMENT PRIMARY KEY, +table_name VARCHAR(100) NOT NULL, +operation_type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL, +record_id VARCHAR(50), +old_values JSON, +new_values JSON, +user_id INT, +session_id VARCHAR(100), +timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, +INDEX idx_table_operation (table_name, operation_type), +INDEX idx_timestamp (timestamp), +INDEX idx_user_session (user_id, session_id) +) ENGINE=InnoDB; +CREATE TABLE t_audit_details ( +detail_id BIGINT AUTO_INCREMENT PRIMARY KEY, +audit_log_id BIGINT, +field_name VARCHAR(100), +old_value TEXT, +new_value TEXT, +change_type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL, +# Test CASCADE on both DELETE and UPDATE +CONSTRAINT fk_audit_detail_log +FOREIGN KEY (audit_log_id) REFERENCES t_audit_log(log_id) +ON DELETE CASCADE ON UPDATE CASCADE, +INDEX idx_audit_log (audit_log_id), +INDEX idx_field_name (field_name) +) ENGINE=InnoDB; +CREATE TABLE `t_audit_details` ( + `detail_id` bigint(20) NOT NULL AUTO_INCREMENT, + `audit_log_id` bigint(20) DEFAULT NULL, + `field_name` varchar(100) DEFAULT NULL, + `old_value` text DEFAULT NULL, + `new_value` text DEFAULT NULL, + `change_type` enum('INSERT','UPDATE','DELETE') NOT NULL, + PRIMARY KEY (`detail_id`), + KEY `idx_audit_log` (`audit_log_id`), + KEY `idx_field_name` (`field_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_audit_details; +DROP TABLE t_audit_log; +CREATE SEQUENCE seq_order_numbers START WITH 1000 INCREMENT BY 1; +CREATE TABLE t_orders ( +order_id INT PRIMARY KEY DEFAULT NEXTVAL(seq_order_numbers), +order_number VARCHAR(20) AS (CONCAT('ORD-', LPAD(order_id, 8, '0'))) STORED, +customer_code VARCHAR(20) NOT NULL, +order_date DATE DEFAULT CURRENT_DATE, +total_amount DECIMAL(15,2) DEFAULT 0.00, +UNIQUE KEY uk_order_number (order_number), +INDEX idx_customer (customer_code), +INDEX idx_order_date (order_date) +) ENGINE=InnoDB; +CREATE TABLE t_order_items ( +item_id BIGINT AUTO_INCREMENT PRIMARY KEY, +order_id INT NOT NULL, +line_number SMALLINT NOT NULL, +product_code VARCHAR(50) NOT NULL, +quantity DECIMAL(10,3) NOT NULL, +unit_price DECIMAL(12,2) NOT NULL, +line_total DECIMAL(15,2) AS (quantity * unit_price) STORED, +discount_percent DECIMAL(5,2) DEFAULT 0.00, +net_amount DECIMAL(15,2) AS (line_total * (100 - discount_percent) / 100) STORED, +# Foreign key referencing table with sequence-based primary key +CONSTRAINT fk_order_item_order +FOREIGN KEY (order_id) REFERENCES t_orders(order_id) +ON DELETE CASCADE ON UPDATE CASCADE, +UNIQUE KEY uk_order_line (order_id, line_number), +INDEX idx_product (product_code), +INDEX idx_line_total (line_total) +) ENGINE=InnoDB; +DROP TABLE t_order_items; +DROP TABLE t_orders; +DROP SEQUENCE seq_order_numbers; +Foreign key constraints test completed +FRM parser should handle all foreign key metadata correctly diff --git a/mysql-test/suite/client/mariadb-frm-foreign-keys.test b/mysql-test/suite/client/mariadb-frm-foreign-keys.test new file mode 100644 index 0000000000000..90ef4c7aa7df2 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-foreign-keys.test @@ -0,0 +1,355 @@ +--echo MariaDB frm parser test - Foreign Key Constraints +--echo Testing foreign key relationships and constraint metadata parsing + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} +# Ensure we're using InnoDB for foreign key support +--source include/have_innodb.inc + +# Test basic single-column foreign key relationships +CREATE TABLE t_parent_basic ( + id INT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + category VARCHAR(50) DEFAULT 'general', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB; + +CREATE TABLE t_child_basic ( + id INT AUTO_INCREMENT PRIMARY KEY, + parent_id INT NOT NULL, + description TEXT, + status ENUM('active', 'inactive', 'pending') DEFAULT 'pending', + FOREIGN KEY fk_parent_basic (parent_id) REFERENCES t_parent_basic(id) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_child_basic.frm +DROP TABLE t_child_basic; +DROP TABLE t_parent_basic; + +# Test foreign key with different actions +CREATE TABLE t_users ( + user_id INT PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(255) UNIQUE, + department_id INT, + manager_id INT, + created_date DATE DEFAULT CURRENT_DATE +) ENGINE=InnoDB; + +CREATE TABLE t_posts ( + post_id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(200) NOT NULL, + content LONGTEXT, + author_id INT NOT NULL, + category_id INT, + published_at DATETIME, + + # Different foreign key actions + CONSTRAINT fk_posts_author + FOREIGN KEY (author_id) REFERENCES t_users(user_id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_posts.frm +DROP TABLE t_posts; +DROP TABLE t_users; + +# Test multi-column foreign key +CREATE TABLE t_companies ( + company_code VARCHAR(10), + branch_code VARCHAR(5), + company_name VARCHAR(100) NOT NULL, + address TEXT, + PRIMARY KEY (company_code, branch_code) +) ENGINE=InnoDB; + +CREATE TABLE t_employees ( + emp_id INT AUTO_INCREMENT PRIMARY KEY, + emp_number VARCHAR(20) UNIQUE, + first_name VARCHAR(50) NOT NULL, + last_name VARCHAR(50) NOT NULL, + company_code VARCHAR(10), + branch_code VARCHAR(5), + department VARCHAR(50), + salary DECIMAL(15,2) DEFAULT 0.00, + hire_date DATE, + + # Multi-column foreign key + CONSTRAINT fk_emp_company_branch + FOREIGN KEY (company_code, branch_code) + REFERENCES t_companies(company_code, branch_code) + ON DELETE RESTRICT ON UPDATE CASCADE, + + INDEX idx_company_branch (company_code, branch_code), + INDEX idx_emp_name (last_name, first_name) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_employees.frm +DROP TABLE t_employees; +DROP TABLE t_companies; + +# Test self-referencing foreign key (tree structure) +CREATE TABLE t_categories ( + category_id INT AUTO_INCREMENT PRIMARY KEY, + category_name VARCHAR(100) NOT NULL, + parent_category_id INT, + level INT DEFAULT 1, + sort_order INT DEFAULT 0, + is_active BOOLEAN DEFAULT TRUE, + description TEXT, + + # Self-referencing foreign key + CONSTRAINT fk_category_parent + FOREIGN KEY (parent_category_id) REFERENCES t_categories(category_id) + ON DELETE CASCADE ON UPDATE CASCADE, + + INDEX idx_parent_category (parent_category_id), + INDEX idx_category_name (category_name), + INDEX idx_sort_order (sort_order) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_categories.frm +DROP TABLE t_categories; + +# Test multiple foreign keys in same table +CREATE TABLE t_departments ( + dept_id INT PRIMARY KEY, + dept_name VARCHAR(100) UNIQUE NOT NULL, + budget DECIMAL(20,2) DEFAULT 0 +) ENGINE=InnoDB; + +CREATE TABLE t_projects ( + project_id INT PRIMARY KEY, + project_name VARCHAR(100) UNIQUE NOT NULL, + start_date DATE, + end_date DATE, + budget DECIMAL(20,2) +) ENGINE=InnoDB; + +CREATE TABLE t_staff ( + staff_id INT AUTO_INCREMENT PRIMARY KEY, + employee_number VARCHAR(20) UNIQUE, + first_name VARCHAR(50) NOT NULL, + last_name VARCHAR(50) NOT NULL, + email VARCHAR(100), + phone VARCHAR(20), + department_id INT, + project_id INT, + manager_id INT, + hire_date DATE, + salary DECIMAL(12,2), + + # Multiple foreign keys with different actions + CONSTRAINT fk_staff_department + FOREIGN KEY (department_id) REFERENCES t_departments(dept_id) + ON DELETE SET NULL ON UPDATE CASCADE, + + CONSTRAINT fk_staff_project + FOREIGN KEY (project_id) REFERENCES t_projects(project_id) + ON DELETE SET NULL ON UPDATE CASCADE, + + CONSTRAINT fk_staff_manager + FOREIGN KEY (manager_id) REFERENCES t_staff(staff_id) + ON DELETE SET NULL ON UPDATE CASCADE, + + INDEX idx_staff_dept (department_id), + INDEX idx_staff_project (project_id), + INDEX idx_staff_manager (manager_id), + INDEX idx_staff_name (last_name, first_name), + INDEX idx_staff_email (email) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_staff.frm +DROP TABLE t_staff; +DROP TABLE t_projects; +DROP TABLE t_departments; + +# Test foreign keys with different data types +CREATE TABLE t_lookup_tables ( + table_id CHAR(5) PRIMARY KEY, + table_name VARCHAR(50) NOT NULL, + description TEXT +) ENGINE=InnoDB; + +CREATE TABLE t_lookup_values ( + value_id INT AUTO_INCREMENT PRIMARY KEY, + table_id CHAR(5), + value_code VARCHAR(20), + value_name VARCHAR(100), + sort_order SMALLINT DEFAULT 0, + is_default BOOLEAN DEFAULT FALSE, + + CONSTRAINT fk_lookup_table + FOREIGN KEY (table_id) REFERENCES t_lookup_tables(table_id) + ON DELETE CASCADE ON UPDATE CASCADE, + + UNIQUE KEY uk_table_code (table_id, value_code), + INDEX idx_sort_order (sort_order) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_lookup_values.frm +DROP TABLE t_lookup_values; +DROP TABLE t_lookup_tables; + +# Test foreign keys with long constraint names and table names +CREATE TABLE t_very_long_parent_table_name_for_testing_frm_parser_limits ( + reference_id BIGINT PRIMARY KEY, + reference_code VARCHAR(50) UNIQUE, + reference_description LONGTEXT, + created_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB; + +CREATE TABLE t_very_long_child_table_name_for_testing_frm_parser_capability ( + child_record_id BIGINT AUTO_INCREMENT PRIMARY KEY, + parent_reference_id BIGINT NOT NULL, + child_specific_data JSON, + processing_status ENUM('new', 'processing', 'completed', 'failed') DEFAULT 'new', + last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + # Long constraint name + CONSTRAINT fk_child_table_parent_ref + FOREIGN KEY (parent_reference_id) + REFERENCES t_very_long_parent_table_name_for_testing_frm_parser_limits(reference_id) + ON DELETE RESTRICT ON UPDATE CASCADE, + + INDEX idx_parent_ref (parent_reference_id), + INDEX idx_status (processing_status) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_very_long_child_table_name_for_testing_frm_parser_capability.frm +DROP TABLE t_very_long_child_table_name_for_testing_frm_parser_capability; +DROP TABLE t_very_long_parent_table_name_for_testing_frm_parser_limits; + +# Test foreign keys with mixed data types and complex relationships +CREATE TABLE t_master_entities ( + entity_uuid CHAR(36) PRIMARY KEY, + entity_type VARCHAR(50) NOT NULL, + entity_name VARCHAR(200) NOT NULL, + version_number INT DEFAULT 1, + is_active TINYINT(1) DEFAULT 1, + metadata JSON, + created_by VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + UNIQUE KEY uk_entity_type_name (entity_type, entity_name), + INDEX idx_entity_type (entity_type), + INDEX idx_active_entities (is_active, entity_type) +) ENGINE=InnoDB; + +CREATE TABLE t_entity_relationships ( + relationship_id BIGINT AUTO_INCREMENT PRIMARY KEY, + source_entity_uuid CHAR(36) NOT NULL, + target_entity_uuid CHAR(36) NOT NULL, + relationship_type VARCHAR(50) NOT NULL, + relationship_strength DECIMAL(3,2) DEFAULT 1.00, + relationship_metadata JSON, + effective_from DATE, + effective_to DATE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + # Multiple foreign keys to same table + CONSTRAINT fk_relationship_source_entity + FOREIGN KEY (source_entity_uuid) + REFERENCES t_master_entities(entity_uuid) + ON DELETE CASCADE ON UPDATE CASCADE, + + CONSTRAINT fk_relationship_target_entity + FOREIGN KEY (target_entity_uuid) + REFERENCES t_master_entities(entity_uuid) + ON DELETE CASCADE ON UPDATE CASCADE, + + # Prevent self-referencing in application logic, but allow in DB + UNIQUE KEY uk_source_target_type (source_entity_uuid, target_entity_uuid, relationship_type), + INDEX idx_source_entity (source_entity_uuid), + INDEX idx_target_entity (target_entity_uuid), + INDEX idx_relationship_type (relationship_type), + INDEX idx_effective_period (effective_from, effective_to) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_entity_relationships.frm +DROP TABLE t_entity_relationships; +DROP TABLE t_master_entities; + +# Test edge case: foreign key with all possible actions +CREATE TABLE t_audit_log ( + log_id BIGINT AUTO_INCREMENT PRIMARY KEY, + table_name VARCHAR(100) NOT NULL, + operation_type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL, + record_id VARCHAR(50), + old_values JSON, + new_values JSON, + user_id INT, + session_id VARCHAR(100), + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + + INDEX idx_table_operation (table_name, operation_type), + INDEX idx_timestamp (timestamp), + INDEX idx_user_session (user_id, session_id) +) ENGINE=InnoDB; + +CREATE TABLE t_audit_details ( + detail_id BIGINT AUTO_INCREMENT PRIMARY KEY, + audit_log_id BIGINT, + field_name VARCHAR(100), + old_value TEXT, + new_value TEXT, + change_type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL, + + # Test CASCADE on both DELETE and UPDATE + CONSTRAINT fk_audit_detail_log + FOREIGN KEY (audit_log_id) REFERENCES t_audit_log(log_id) + ON DELETE CASCADE ON UPDATE CASCADE, + + INDEX idx_audit_log (audit_log_id), + INDEX idx_field_name (field_name) +) ENGINE=InnoDB; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_audit_details.frm +DROP TABLE t_audit_details; +DROP TABLE t_audit_log; + +# Test foreign key with computed columns and sequences +CREATE SEQUENCE seq_order_numbers START WITH 1000 INCREMENT BY 1; + +CREATE TABLE t_orders ( + order_id INT PRIMARY KEY DEFAULT NEXTVAL(seq_order_numbers), + order_number VARCHAR(20) AS (CONCAT('ORD-', LPAD(order_id, 8, '0'))) STORED, + customer_code VARCHAR(20) NOT NULL, + order_date DATE DEFAULT CURRENT_DATE, + total_amount DECIMAL(15,2) DEFAULT 0.00, + + UNIQUE KEY uk_order_number (order_number), + INDEX idx_customer (customer_code), + INDEX idx_order_date (order_date) +) ENGINE=InnoDB; + +CREATE TABLE t_order_items ( + item_id BIGINT AUTO_INCREMENT PRIMARY KEY, + order_id INT NOT NULL, + line_number SMALLINT NOT NULL, + product_code VARCHAR(50) NOT NULL, + quantity DECIMAL(10,3) NOT NULL, + unit_price DECIMAL(12,2) NOT NULL, + line_total DECIMAL(15,2) AS (quantity * unit_price) STORED, + discount_percent DECIMAL(5,2) DEFAULT 0.00, + net_amount DECIMAL(15,2) AS (line_total * (100 - discount_percent) / 100) STORED, + + # Foreign key referencing table with sequence-based primary key + CONSTRAINT fk_order_item_order + FOREIGN KEY (order_id) REFERENCES t_orders(order_id) + ON DELETE CASCADE ON UPDATE CASCADE, + + UNIQUE KEY uk_order_line (order_id, line_number), + INDEX idx_product (product_code), + INDEX idx_line_total (line_total) +) ENGINE=InnoDB; +-- error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_order_items.frm +DROP TABLE t_order_items; +DROP TABLE t_orders; +DROP SEQUENCE seq_order_numbers; + +--echo Foreign key constraints test completed +--echo FRM parser should handle all foreign key metadata correctly diff --git a/mysql-test/suite/client/mariadb-frm-indexes.result b/mysql-test/suite/client/mariadb-frm-indexes.result new file mode 100644 index 0000000000000..e6dadef380fff --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-indexes.result @@ -0,0 +1,149 @@ +MariaDB frm parser test - Index Types +Testing various index types and configurations +CREATE TABLE t_btree_indexes ( +id INT PRIMARY KEY, +name VARCHAR(100), +email VARCHAR(100), +age INT, +score DECIMAL(5,2), +INDEX idx_name (name) USING BTREE, +UNIQUE KEY idx_email (email) USING BTREE, +INDEX idx_age_score (age, score) USING BTREE +); +CREATE TABLE `t_btree_indexes` ( + `id` int(11) NOT NULL, + `name` varchar(100) DEFAULT NULL, + `email` varchar(100) DEFAULT NULL, + `age` int(11) DEFAULT NULL, + `score` decimal(5,2) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_email` (`email`) USING BTREE, + KEY `idx_name` (`name`) USING BTREE, + KEY `idx_age_score` (`age`,`score`) USING BTREE +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_btree_indexes; +CREATE TABLE t_hash_indexes ( +id INT, +code VARCHAR(10), +category VARCHAR(20), +INDEX idx_code (code) USING HASH, +INDEX idx_category (category) USING HASH +) ENGINE=MEMORY; +CREATE TABLE `t_hash_indexes` ( + `id` int(11) DEFAULT NULL, + `code` varchar(10) DEFAULT NULL, + `category` varchar(20) DEFAULT NULL, + KEY `idx_code` (`code`) USING HASH, + KEY `idx_category` (`category`) USING HASH +) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_hash_indexes; +CREATE TABLE t_compound_pk ( +tenant_id INT, +user_id INT, +name VARCHAR(50), +PRIMARY KEY (tenant_id, user_id), +INDEX idx_name (name) +); +CREATE TABLE `t_compound_pk` ( + `tenant_id` int(11) NOT NULL, + `user_id` int(11) NOT NULL, + `name` varchar(50) DEFAULT NULL, + PRIMARY KEY (`tenant_id`,`user_id`), + KEY `idx_name` (`name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_compound_pk; +CREATE TABLE t_index_options ( +id INT AUTO_INCREMENT, +title VARCHAR(200), +content TEXT, +status TINYINT, +created_at DATETIME, +PRIMARY KEY (id), +INDEX idx_title_partial (title(50)), +INDEX idx_status_created (status, created_at), +FULLTEXT KEY ft_content (content) +); +CREATE TABLE `t_index_options` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(200) DEFAULT NULL, + `content` text DEFAULT NULL, + `status` tinyint(4) DEFAULT NULL, + `created_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_title_partial` (`title`(50)), + KEY `idx_status_created` (`status`,`created_at`), + FULLTEXT KEY `ft_content` (`content`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_index_options; +CREATE TABLE t_spatial_indexes ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(100), +location POINT NOT NULL, +area POLYGON NOT NULL, +SPATIAL INDEX idx_location (location), +SPATIAL INDEX idx_area (area) +) ENGINE=MyISAM; +CREATE TABLE `t_spatial_indexes` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `location` point NOT NULL, + `area` polygon NOT NULL, + PRIMARY KEY (`id`), + SPATIAL KEY `idx_location` (`location`), + SPATIAL KEY `idx_area` (`area`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_spatial_indexes; +CREATE TABLE t_unique_indexes ( +id INT, +username VARCHAR(50), +email VARCHAR(100), +phone VARCHAR(20), +ssn VARCHAR(11), +UNIQUE KEY idx_username (username), +UNIQUE KEY idx_email (email), +UNIQUE KEY idx_phone_ssn (phone, ssn), +INDEX idx_id (id) +); +CREATE TABLE `t_unique_indexes` ( + `id` int(11) DEFAULT NULL, + `username` varchar(50) DEFAULT NULL, + `email` varchar(100) DEFAULT NULL, + `phone` varchar(20) DEFAULT NULL, + `ssn` varchar(11) DEFAULT NULL, + UNIQUE KEY `idx_username` (`username`), + UNIQUE KEY `idx_email` (`email`), + UNIQUE KEY `idx_phone_ssn` (`phone`,`ssn`), + KEY `idx_id` (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_unique_indexes; +CREATE TABLE t_desc_indexes ( +id INT, +created_at TIMESTAMP, +priority INT, +INDEX idx_created_desc (created_at DESC), +INDEX idx_priority_created (priority ASC, created_at DESC) +); +CREATE TABLE `t_desc_indexes` ( + `id` int(11) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `priority` int(11) DEFAULT NULL, + KEY `idx_created_desc` (`created_at` DESC), + KEY `idx_priority_created` (`priority`,`created_at` DESC) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_desc_indexes; +CREATE TABLE t_ignored_indexes ( +id INT, +name VARCHAR(100), +status INT, +INDEX idx_name (name), +INDEX idx_status (status) IGNORED +); +CREATE TABLE `t_ignored_indexes` ( + `id` int(11) DEFAULT NULL, + `name` varchar(100) DEFAULT NULL, + `status` int(11) DEFAULT NULL, + KEY `idx_name` (`name`), + KEY `idx_status` (`status`) IGNORED +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_ignored_indexes; +Index types test completed diff --git a/mysql-test/suite/client/mariadb-frm-indexes.test b/mysql-test/suite/client/mariadb-frm-indexes.test new file mode 100644 index 0000000000000..9760c69f2ad20 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-indexes.test @@ -0,0 +1,119 @@ +--echo MariaDB frm parser test - Index Types +--echo Testing various index types and configurations + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} + + +# Test basic BTREE indexes +CREATE TABLE t_btree_indexes ( + id INT PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + age INT, + score DECIMAL(5,2), + INDEX idx_name (name) USING BTREE, + UNIQUE KEY idx_email (email) USING BTREE, + INDEX idx_age_score (age, score) USING BTREE +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_btree_indexes.frm +DROP TABLE t_btree_indexes; + +# Test HASH indexes (for MEMORY engine) +CREATE TABLE t_hash_indexes ( + id INT, + code VARCHAR(10), + category VARCHAR(20), + INDEX idx_code (code) USING HASH, + INDEX idx_category (category) USING HASH +) ENGINE=MEMORY; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_hash_indexes.frm +DROP TABLE t_hash_indexes; + +# Test compound primary key +CREATE TABLE t_compound_pk ( + tenant_id INT, + user_id INT, + name VARCHAR(50), + PRIMARY KEY (tenant_id, user_id), + INDEX idx_name (name) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_compound_pk.frm +DROP TABLE t_compound_pk; + +# Test various index options +CREATE TABLE t_index_options ( + id INT AUTO_INCREMENT, + title VARCHAR(200), + content TEXT, + status TINYINT, + created_at DATETIME, + PRIMARY KEY (id), + INDEX idx_title_partial (title(50)), + INDEX idx_status_created (status, created_at), + FULLTEXT KEY ft_content (content) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_index_options.frm +DROP TABLE t_index_options; + +# Test spatial indexes (RTREE) - requires MyISAM or compatible engine +CREATE TABLE t_spatial_indexes ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + location POINT NOT NULL, + area POLYGON NOT NULL, + SPATIAL INDEX idx_location (location), + SPATIAL INDEX idx_area (area) +) ENGINE=MyISAM; +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_spatial_indexes.frm +DROP TABLE t_spatial_indexes; + +# Test unique indexes and constraints +CREATE TABLE t_unique_indexes ( + id INT, + username VARCHAR(50), + email VARCHAR(100), + phone VARCHAR(20), + ssn VARCHAR(11), + UNIQUE KEY idx_username (username), + UNIQUE KEY idx_email (email), + UNIQUE KEY idx_phone_ssn (phone, ssn), + INDEX idx_id (id) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_unique_indexes.frm +DROP TABLE t_unique_indexes; + +# Test descending indexes (if supported) +CREATE TABLE t_desc_indexes ( + id INT, + created_at TIMESTAMP, + priority INT, + INDEX idx_created_desc (created_at DESC), + INDEX idx_priority_created (priority ASC, created_at DESC) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_desc_indexes.frm +DROP TABLE t_desc_indexes; + +# Test ignored indexes (MariaDB 10.6+) +CREATE TABLE t_ignored_indexes ( + id INT, + name VARCHAR(100), + status INT, + INDEX idx_name (name), + INDEX idx_status (status) IGNORED +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_ignored_indexes.frm +DROP TABLE t_ignored_indexes; + +--echo Index types test completed diff --git a/mysql-test/suite/client/mariadb-frm-partitions.result b/mysql-test/suite/client/mariadb-frm-partitions.result new file mode 100644 index 0000000000000..abeabbf02ec2b --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-partitions.result @@ -0,0 +1,126 @@ +MariaDB frm parser test - Partitions +Testing various partition types and configurations +CREATE TABLE t_range_partition ( +id INT, +sale_date DATE, +amount DECIMAL(10,2), +region VARCHAR(50) +) PARTITION BY RANGE (YEAR(sale_date)) ( +PARTITION p2020 VALUES LESS THAN (2021), +PARTITION p2021 VALUES LESS THAN (2022), +PARTITION p2022 VALUES LESS THAN (2023), +PARTITION p2023 VALUES LESS THAN (2024), +PARTITION p_future VALUES LESS THAN MAXVALUE +); +DROP TABLE t_range_partition; +CREATE TABLE t_hash_partition ( +id INT, +user_id INT, +data VARCHAR(255), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +PRIMARY KEY (user_id, id) +) PARTITION BY HASH(user_id) PARTITIONS 4; +DROP TABLE t_hash_partition; +CREATE TABLE t_list_partition ( +id INT, +country VARCHAR(20), +city VARCHAR(50), +population INT +) PARTITION BY LIST COLUMNS(country) ( +PARTITION p_north_america VALUES IN ('USA', 'Canada', 'Mexico'), +PARTITION p_europe VALUES IN ('Germany', 'France', 'UK', 'Spain'), +PARTITION p_asia VALUES IN ('Japan', 'China', 'India', 'Korea') +); +DROP TABLE t_list_partition; +CREATE TABLE t_key_partition ( +id INT, +name VARCHAR(100), +email VARCHAR(255), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +PRIMARY KEY (id, name) +) PARTITION BY KEY(id) PARTITIONS 8; +DROP TABLE t_key_partition; +CREATE TABLE t_range_columns_partition ( +id INT, +first_name VARCHAR(50), +last_name VARCHAR(50), +birth_date DATE +) PARTITION BY RANGE COLUMNS(last_name) ( +PARTITION p1 VALUES LESS THAN ('F'), +PARTITION p2 VALUES LESS THAN ('L'), +PARTITION p3 VALUES LESS THAN ('S'), +PARTITION p4 VALUES LESS THAN (MAXVALUE) +); +DROP TABLE t_range_columns_partition; +CREATE TABLE t_subpartition ( +id INT, +sale_date DATE, +region VARCHAR(20), +amount DECIMAL(10,2) +) PARTITION BY RANGE (YEAR(sale_date)) +SUBPARTITION BY HASH(id) +SUBPARTITIONS 2 ( +PARTITION p2022 VALUES LESS THAN (2023), +PARTITION p2023 VALUES LESS THAN (2024), +PARTITION p_future VALUES LESS THAN MAXVALUE +); +DROP TABLE t_subpartition; +CREATE TABLE t_named_subpartitions ( +id INT, +order_date DATE, +customer_id INT, +total DECIMAL(10,2) +) PARTITION BY RANGE (YEAR(order_date)) +SUBPARTITION BY HASH(customer_id) ( +PARTITION p2022 VALUES LESS THAN (2023) ( +SUBPARTITION p2022_s1, +SUBPARTITION p2022_s2 +), +PARTITION p2023 VALUES LESS THAN (2024) ( +SUBPARTITION p2023_s1, +SUBPARTITION p2023_s2 +) +); +DROP TABLE t_named_subpartitions; +CREATE TABLE t_list_multi_columns ( +id INT, +country VARCHAR(20), +region VARCHAR(20), +city VARCHAR(50) +) PARTITION BY LIST COLUMNS(country, region) ( +PARTITION p_us_east VALUES IN (('USA', 'East')), +PARTITION p_us_west VALUES IN (('USA', 'West')), +PARTITION p_canada VALUES IN (('Canada', 'East'), ('Canada', 'West')) +); +DROP TABLE t_list_multi_columns; +CREATE TABLE t_partition_with_indexes ( +id INT AUTO_INCREMENT, +user_id INT, +name VARCHAR(100), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +PRIMARY KEY (id, user_id), +INDEX idx_name (name), +INDEX idx_created (created_at) +) PARTITION BY HASH(user_id) PARTITIONS 4; +DROP TABLE t_partition_with_indexes; +CREATE TABLE t_partition_innodb ( +id INT AUTO_INCREMENT, +data VARCHAR(255), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +PRIMARY KEY (id) +) ENGINE=InnoDB +PARTITION BY HASH(id) PARTITIONS 3; +DROP TABLE t_partition_innodb; +CREATE TABLE t_linear_hash_partition ( +id INT AUTO_INCREMENT PRIMARY KEY, +data VARCHAR(255), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) PARTITION BY LINEAR HASH(id) PARTITIONS 6; +DROP TABLE t_linear_hash_partition; +CREATE TABLE t_linear_key_partition ( +id INT, +name VARCHAR(100), +PRIMARY KEY (id) +) PARTITION BY LINEAR KEY(id) PARTITIONS 5; +DROP TABLE t_linear_key_partition; +Partitions test completed diff --git a/mysql-test/suite/client/mariadb-frm-partitions.test b/mysql-test/suite/client/mariadb-frm-partitions.test new file mode 100644 index 0000000000000..cafb1c04cbf10 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-partitions.test @@ -0,0 +1,191 @@ +--echo MariaDB frm parser test - Partitions +--echo Testing various partition types and configurations + +--source include/have_partition.inc +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` + +# Test RANGE partitioning +CREATE TABLE t_range_partition ( + id INT, + sale_date DATE, + amount DECIMAL(10,2), + region VARCHAR(50) +) PARTITION BY RANGE (YEAR(sale_date)) ( + PARTITION p2020 VALUES LESS THAN (2021), + PARTITION p2021 VALUES LESS THAN (2022), + PARTITION p2022 VALUES LESS THAN (2023), + PARTITION p2023 VALUES LESS THAN (2024), + PARTITION p_future VALUES LESS THAN MAXVALUE +); +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_range_partition.frm +DROP TABLE t_range_partition; + +# Test HASH partitioning +CREATE TABLE t_hash_partition ( + id INT, + user_id INT, + data VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, id) +) PARTITION BY HASH(user_id) PARTITIONS 4; +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_hash_partition.frm +DROP TABLE t_hash_partition; + +# Test LIST partitioning +CREATE TABLE t_list_partition ( + id INT, + country VARCHAR(20), + city VARCHAR(50), + population INT +) PARTITION BY LIST COLUMNS(country) ( + PARTITION p_north_america VALUES IN ('USA', 'Canada', 'Mexico'), + PARTITION p_europe VALUES IN ('Germany', 'France', 'UK', 'Spain'), + PARTITION p_asia VALUES IN ('Japan', 'China', 'India', 'Korea') +); +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_list_partition.frm +DROP TABLE t_list_partition; + +# Test KEY partitioning +CREATE TABLE t_key_partition ( + id INT, + name VARCHAR(100), + email VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id, name) +) PARTITION BY KEY(id) PARTITIONS 8; +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_key_partition.frm +DROP TABLE t_key_partition; + +# Test RANGE COLUMNS partitioning +CREATE TABLE t_range_columns_partition ( + id INT, + first_name VARCHAR(50), + last_name VARCHAR(50), + birth_date DATE +) PARTITION BY RANGE COLUMNS(last_name) ( + PARTITION p1 VALUES LESS THAN ('F'), + PARTITION p2 VALUES LESS THAN ('L'), + PARTITION p3 VALUES LESS THAN ('S'), + PARTITION p4 VALUES LESS THAN (MAXVALUE) +); +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_range_columns_partition.frm +DROP TABLE t_range_columns_partition; + +# Test subpartitioning (RANGE + HASH) +CREATE TABLE t_subpartition ( + id INT, + sale_date DATE, + region VARCHAR(20), + amount DECIMAL(10,2) +) PARTITION BY RANGE (YEAR(sale_date)) + SUBPARTITION BY HASH(id) + SUBPARTITIONS 2 ( + PARTITION p2022 VALUES LESS THAN (2023), + PARTITION p2023 VALUES LESS THAN (2024), + PARTITION p_future VALUES LESS THAN MAXVALUE + ); +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_subpartition.frm +DROP TABLE t_subpartition; + +# Test partitioning with explicit subpartition names +CREATE TABLE t_named_subpartitions ( + id INT, + order_date DATE, + customer_id INT, + total DECIMAL(10,2) +) PARTITION BY RANGE (YEAR(order_date)) + SUBPARTITION BY HASH(customer_id) ( + PARTITION p2022 VALUES LESS THAN (2023) ( + SUBPARTITION p2022_s1, + SUBPARTITION p2022_s2 + ), + PARTITION p2023 VALUES LESS THAN (2024) ( + SUBPARTITION p2023_s1, + SUBPARTITION p2023_s2 + ) + ); +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_named_subpartitions.frm +DROP TABLE t_named_subpartitions; + +# Test LIST COLUMNS with multiple columns +CREATE TABLE t_list_multi_columns ( + id INT, + country VARCHAR(20), + region VARCHAR(20), + city VARCHAR(50) +) PARTITION BY LIST COLUMNS(country, region) ( + PARTITION p_us_east VALUES IN (('USA', 'East')), + PARTITION p_us_west VALUES IN (('USA', 'West')), + PARTITION p_canada VALUES IN (('Canada', 'East'), ('Canada', 'West')) +); +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_list_multi_columns.frm +DROP TABLE t_list_multi_columns; + +# Test partitioning with indexes +CREATE TABLE t_partition_with_indexes ( + id INT AUTO_INCREMENT, + user_id INT, + name VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id, user_id), + INDEX idx_name (name), + INDEX idx_created (created_at) +) PARTITION BY HASH(user_id) PARTITIONS 4; +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_partition_with_indexes.frm +DROP TABLE t_partition_with_indexes; + +# Test partitioning with different storage engines +--source include/have_innodb.inc +CREATE TABLE t_partition_innodb ( + id INT AUTO_INCREMENT, + data VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +) ENGINE=InnoDB + PARTITION BY HASH(id) PARTITIONS 3; +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_partition_innodb.frm +DROP TABLE t_partition_innodb; + +# Test LINEAR HASH partitioning +CREATE TABLE t_linear_hash_partition ( + id INT AUTO_INCREMENT PRIMARY KEY, + data VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) PARTITION BY LINEAR HASH(id) PARTITIONS 6; +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_linear_hash_partition.frm +DROP TABLE t_linear_hash_partition; + +# Test LINEAR KEY partitioning +CREATE TABLE t_linear_key_partition ( + id INT, + name VARCHAR(100), + PRIMARY KEY (id) +) PARTITION BY LINEAR KEY(id) PARTITIONS 5; +--error 1 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_linear_key_partition.frm +DROP TABLE t_linear_key_partition; + +--echo Partitions test completed diff --git a/mysql-test/suite/client/mariadb-frm-sequences.result b/mysql-test/suite/client/mariadb-frm-sequences.result new file mode 100644 index 0000000000000..a460bf808c8f8 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-sequences.result @@ -0,0 +1,304 @@ +MariaDB frm parser test - Sequence Default Expressions +Testing sequence integration in DEFAULT expressions and various contexts +CREATE SEQUENCE seq_basic START WITH 1000 INCREMENT BY 1; +CREATE TABLE t_basic_sequence ( +id INT DEFAULT NEXTVAL(seq_basic) PRIMARY KEY, +name VARCHAR(100), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE TABLE `t_basic_sequence` ( + `id` int(11) NOT NULL DEFAULT nextval(`test`.`seq_basic`), + `name` varchar(100) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0), + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_basic_sequence; +DROP SEQUENCE seq_basic; +CREATE SEQUENCE seq_custom START WITH 5000 INCREMENT BY 10 MINVALUE 1000 MAXVALUE 999999; +CREATE TABLE t_custom_sequence ( +order_id INT DEFAULT NEXTVAL(seq_custom), +batch_id INT DEFAULT NEXTVAL(seq_custom), +item_code VARCHAR(50), +quantity INT DEFAULT 1 +); +CREATE TABLE `t_custom_sequence` ( + `order_id` int(11) DEFAULT nextval(`test`.`seq_custom`), + `batch_id` int(11) DEFAULT nextval(`test`.`seq_custom`), + `item_code` varchar(50) DEFAULT NULL, + `quantity` int(11) DEFAULT 1 +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_custom_sequence; +DROP SEQUENCE seq_custom; +CREATE SEQUENCE seq_orders START WITH 10000 INCREMENT BY 1; +CREATE SEQUENCE seq_items START WITH 50000 INCREMENT BY 5; +CREATE SEQUENCE seq_batches START WITH 1 INCREMENT BY 1; +CREATE TABLE t_multiple_sequences ( +order_id INT DEFAULT NEXTVAL(seq_orders), +item_id INT DEFAULT NEXTVAL(seq_items), +batch_id INT DEFAULT NEXTVAL(seq_batches), +description TEXT, +price DECIMAL(10,2) DEFAULT 0.00 +); +CREATE TABLE `t_multiple_sequences` ( + `order_id` int(11) DEFAULT nextval(`test`.`seq_orders`), + `item_id` int(11) DEFAULT nextval(`test`.`seq_items`), + `batch_id` int(11) DEFAULT nextval(`test`.`seq_batches`), + `description` text DEFAULT NULL, + `price` decimal(10,2) DEFAULT 0.00 +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_multiple_sequences; +DROP SEQUENCE seq_orders; +DROP SEQUENCE seq_items; +DROP SEQUENCE seq_batches; +CREATE SEQUENCE seq_computed START WITH 100 INCREMENT BY 2; +CREATE TABLE t_computed_sequence ( +id INT, +base_number INT DEFAULT NEXTVAL(seq_computed), +doubled_value INT AS (base_number * 2) VIRTUAL, +formatted_id VARCHAR(20) AS (CONCAT('ID-', LPAD(base_number, 6, '0'))) STORED, +is_even BOOLEAN AS (base_number % 2 = 0) VIRTUAL, +range_category VARCHAR(20) AS ( +CASE +WHEN base_number < 200 THEN 'LOW' + WHEN base_number < 500 THEN 'MEDIUM' + ELSE 'HIGH' + END +) VIRTUAL +); +DROP TABLE t_computed_sequence; +DROP SEQUENCE seq_computed; +CREATE SEQUENCE seq_cycle START WITH 1 INCREMENT BY 1 MAXVALUE 5 CYCLE; +CREATE TABLE t_cycle_sequence ( +id INT AUTO_INCREMENT PRIMARY KEY, +cycle_value INT DEFAULT NEXTVAL(seq_cycle), +data VARCHAR(100), +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE TABLE `t_cycle_sequence` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `cycle_value` int(11) DEFAULT nextval(`test`.`seq_cycle`), + `data` varchar(100) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(0), + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_cycle_sequence; +DROP SEQUENCE seq_cycle; +CREATE SEQUENCE seq_cached START WITH 1000 INCREMENT BY 1 CACHE 20; +CREATE TABLE t_cached_sequence ( +transaction_id INT DEFAULT NEXTVAL(seq_cached), +reference_id INT DEFAULT NEXTVAL(seq_cached), +amount DECIMAL(15,2), +currency VARCHAR(3) DEFAULT 'USD' +); +CREATE TABLE `t_cached_sequence` ( + `transaction_id` int(11) DEFAULT nextval(`test`.`seq_cached`), + `reference_id` int(11) DEFAULT nextval(`test`.`seq_cached`), + `amount` decimal(15,2) DEFAULT NULL, + `currency` varchar(3) DEFAULT 'USD' +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_cached_sequence; +DROP SEQUENCE seq_cached; +CREATE SEQUENCE seq_mixed START WITH 1 INCREMENT BY 1; +CREATE TABLE t_mixed_sequence_defaults ( +id INT DEFAULT NEXTVAL(seq_mixed), +uuid_val VARCHAR(36) DEFAULT (UUID()), +random_val INT DEFAULT (FLOOR(RAND() * 1000)), +timestamp_val TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +computed_val INT AS (id * 100 + random_val) STORED, +formatted_key VARCHAR(50) AS (CONCAT('KEY-', id, '-', SUBSTRING(uuid_val, 1, 8))) VIRTUAL +); +DROP TABLE t_mixed_sequence_defaults; +DROP SEQUENCE seq_mixed; +CREATE SEQUENCE seq_bigint START WITH 9223372036854775800 INCREMENT BY 1; +CREATE TABLE t_sequence_datatypes ( +tiny_seq TINYINT DEFAULT (NEXTVAL(seq_bigint) % 127), +small_seq SMALLINT DEFAULT (NEXTVAL(seq_bigint) % 32767), +int_seq INT DEFAULT (NEXTVAL(seq_bigint) % 2147483647), +bigint_seq BIGINT DEFAULT NEXTVAL(seq_bigint), +decimal_seq DECIMAL(20,2) DEFAULT (NEXTVAL(seq_bigint) / 100.0), +float_seq FLOAT DEFAULT (NEXTVAL(seq_bigint) * 0.001) +); +CREATE TABLE `t_sequence_datatypes` ( + `tiny_seq` tinyint(4) DEFAULT nextval(`test`.`seq_bigint`) MOD 127, + `small_seq` smallint(6) DEFAULT nextval(`test`.`seq_bigint`) MOD 32767, + `int_seq` int(11) DEFAULT nextval(`test`.`seq_bigint`) MOD 2147483647, + `bigint_seq` bigint(20) DEFAULT nextval(`test`.`seq_bigint`), + `decimal_seq` decimal(20,2) DEFAULT nextval(`test`.`seq_bigint`) / 100.0, + `float_seq` float DEFAULT nextval(`test`.`seq_bigint`) * 0.001 +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_sequence_datatypes; +DROP SEQUENCE seq_bigint; +CREATE SEQUENCE seq_conditional START WITH 1 INCREMENT BY 1; +CREATE TABLE t_conditional_sequence ( +id INT, +priority INT DEFAULT ( +CASE +WHEN NEXTVAL(seq_conditional) % 10 = 0 THEN 3 +WHEN NEXTVAL(seq_conditional) % 5 = 0 THEN 2 +ELSE 1 +END +), +sequence_val INT DEFAULT NEXTVAL(seq_conditional), +status VARCHAR(20) DEFAULT ( +CASE +WHEN sequence_val % 3 = 0 THEN 'APPROVED' + WHEN sequence_val % 3 = 1 THEN 'PENDING' + ELSE 'REJECTED' + END +) +); +CREATE TABLE `t_conditional_sequence` ( + `id` int(11) DEFAULT NULL, + `priority` int(11) DEFAULT case when nextval(`test`.`seq_conditional`) MOD 10 = 0 then 3 when nextval(`test`.`seq_conditional`) MOD 5 = 0 then 2 else 1 end, + `sequence_val` int(11) DEFAULT nextval(`test`.`seq_conditional`), + `status` varchar(20) DEFAULT case when `sequence_val` MOD 3 = 0 then 'APPROVED' when `sequence_val` MOD 3 = 1 then 'PENDING' else 'REJECTED' end +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_conditional_sequence; +DROP SEQUENCE seq_conditional; +CREATE SEQUENCE seq_math START WITH 10 INCREMENT BY 3; +CREATE TABLE t_sequence_math ( +base_val INT DEFAULT NEXTVAL(seq_math), +squared_val BIGINT AS (base_val * base_val) VIRTUAL, +cubed_val BIGINT AS (base_val * base_val * base_val) STORED, +factorial_approx DOUBLE AS ( +CASE +WHEN base_val <= 1 THEN 1 +WHEN base_val <= 10 THEN POWER(base_val, base_val/2) +ELSE POWER(base_val, LOG(base_val)) +END +) VIRTUAL, +fibonacci_approx INT AS ( +CASE +WHEN base_val <= 2 THEN 1 +ELSE FLOOR(POWER(((1 + SQRT(5))/2), base_val) / SQRT(5) + 0.5) +END +) VIRTUAL, +prime_check BOOLEAN AS ( +base_val > 1 AND ( +base_val = 2 OR base_val = 3 OR base_val = 5 OR base_val = 7 OR +(base_val % 2 != 0 AND base_val % 3 != 0 AND base_val % 5 != 0 AND base_val % 7 != 0) +) +) VIRTUAL +); +DROP TABLE t_sequence_math; +DROP SEQUENCE seq_math; +CREATE SEQUENCE seq_string START WITH 1 INCREMENT BY 1; +CREATE TABLE t_sequence_string ( +id INT DEFAULT NEXTVAL(seq_string), +padded_id VARCHAR(10) AS (LPAD(id, 8, '0')) VIRTUAL, +hex_id VARCHAR(20) AS (CONCAT('0x', HEX(id))) VIRTUAL, +binary_id VARCHAR(64) AS (BIN(id)) VIRTUAL, +encoded_id VARCHAR(50) AS (TO_BASE64(CONCAT('ID:', id))) STORED, +checksum_id VARCHAR(50) AS (SHA2(CONCAT('prefix:', id, ':suffix'), 256)) VIRTUAL +); +DROP TABLE t_sequence_string; +DROP SEQUENCE seq_string; +CREATE SEQUENCE seq_datetime START WITH 1 INCREMENT BY 1; +CREATE TABLE t_sequence_datetime ( +seq_id INT DEFAULT NEXTVAL(seq_datetime), +base_date DATE DEFAULT CURRENT_DATE, +computed_date DATE AS (DATE_ADD(base_date, INTERVAL seq_id DAY)) VIRTUAL, +computed_timestamp DATETIME AS (DATE_ADD(NOW(), INTERVAL seq_id MINUTE)) VIRTUAL, +week_offset DATE AS (DATE_ADD(base_date, INTERVAL seq_id WEEK)) STORED, +year_offset YEAR AS (YEAR(DATE_ADD(base_date, INTERVAL seq_id YEAR))) VIRTUAL, +time_based VARCHAR(20) AS ( +CASE (seq_id % 4) +WHEN 0 THEN 'MORNING' + WHEN 1 THEN 'AFTERNOON' +WHEN 2 THEN 'EVENING' + ELSE 'NIGHT' + END +) VIRTUAL +); +DROP TABLE t_sequence_datetime; +DROP SEQUENCE seq_datetime; +CREATE SEQUENCE seq_nested START WITH 1 INCREMENT BY 1; +CREATE TABLE t_nested_sequence_expressions ( +primary_seq INT DEFAULT NEXTVAL(seq_nested), +nested_calc INT AS ( +CASE +WHEN primary_seq <= 10 THEN primary_seq * 2 +WHEN primary_seq <= 100 THEN ( +CASE +WHEN primary_seq % 10 = 0 THEN primary_seq / 10 +ELSE primary_seq +END +) +ELSE FLOOR(SQRT(primary_seq)) +END +) STORED, +multi_level_check BOOLEAN AS ( +(primary_seq > 0) AND +(nested_calc > 0) AND +(CASE +WHEN primary_seq < 50 THEN (nested_calc < primary_seq) +ELSE (nested_calc > primary_seq) +END) +) VIRTUAL, +formatted_result VARCHAR(100) AS ( +CONCAT( +'SEQ:', primary_seq, +'|CALC:', nested_calc, +'|RATIO:', ROUND(nested_calc/primary_seq, 2), +'|VALID:', IF(multi_level_check, 'YES', 'NO') +) +) VIRTUAL +); +DROP TABLE t_nested_sequence_expressions; +DROP SEQUENCE seq_nested; +CREATE SEQUENCE seq_json START WITH 1 INCREMENT BY 1 MAXVALUE 1000; +CREATE TABLE t_sequence_json ( +id INT DEFAULT NEXTVAL(seq_json), +metadata JSON DEFAULT (JSON_OBJECT( +'seq_id', NEXTVAL(seq_json), +'created_at', NOW(), +'version', 1 +)), +extracted_seq INT AS (JSON_EXTRACT(metadata, '$.seq_id')) VIRTUAL, +seq_diff INT AS (id - JSON_EXTRACT(metadata, '$.seq_id')) STORED, +enhanced_metadata JSON AS ( +JSON_SET( +metadata, +'$.computed.id_sum', id + JSON_EXTRACT(metadata, '$.seq_id'), +'$.computed.id_product', id * JSON_EXTRACT(metadata, '$.seq_id'), +'$.computed.relationship', +CASE +WHEN id > JSON_EXTRACT(metadata, '$.seq_id') THEN 'GREATER' + WHEN id < JSON_EXTRACT(metadata, '$.seq_id') THEN 'LESS' + ELSE 'EQUAL' + END +) +) VIRTUAL +); +DROP TABLE t_sequence_json; +DROP SEQUENCE seq_json; +CREATE SEQUENCE seq_negative START WITH 1000 INCREMENT BY -1 MINVALUE 1 MAXVALUE 1000; +CREATE TABLE t_negative_sequence ( +countdown INT DEFAULT NEXTVAL(seq_negative), +remaining_pct DECIMAL(5,2) AS ((countdown / 1000.0) * 100) VIRTUAL, +status_level VARCHAR(10) AS ( +CASE +WHEN countdown > 800 THEN 'HIGH' + WHEN countdown > 500 THEN 'MEDIUM' + WHEN countdown > 200 THEN 'LOW' + ELSE 'CRITICAL' + END +) STORED, +is_milestone BOOLEAN AS (countdown % 100 = 0) VIRTUAL +); +DROP TABLE t_negative_sequence; +DROP SEQUENCE seq_negative; +CREATE SEQUENCE seq_complex_math START WITH 1 INCREMENT BY 1; +CREATE TABLE t_complex_sequence_math ( +n INT DEFAULT NEXTVAL(seq_complex_math), +harmonic_series DOUBLE AS (1.0/n) VIRTUAL, +geometric_series DOUBLE AS (POWER(0.5, n)) VIRTUAL, +log_series DOUBLE AS (CASE WHEN n > 0 THEN LOG(n) ELSE NULL END) VIRTUAL, +trig_approx DOUBLE AS (SIN(n * PI()/180)) STORED, +exponential_decay DOUBLE AS (EXP(-n/10.0)) VIRTUAL, +polynomial_val DOUBLE AS (n*n*n - 3*n*n + 2*n + 1) VIRTUAL, +modular_arithmetic INT AS ((n * n + 7*n + 12) % 1000) VIRTUAL +); +DROP TABLE t_complex_sequence_math; +DROP SEQUENCE seq_complex_math; +Sequence default expressions test completed diff --git a/mysql-test/suite/client/mariadb-frm-sequences.test b/mysql-test/suite/client/mariadb-frm-sequences.test new file mode 100644 index 0000000000000..02a4add14b079 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-sequences.test @@ -0,0 +1,330 @@ +--echo MariaDB frm parser test - Sequence Default Expressions +--echo Testing sequence integration in DEFAULT expressions and various contexts + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} +# Test basic sequence defaults +CREATE SEQUENCE seq_basic START WITH 1000 INCREMENT BY 1; +CREATE TABLE t_basic_sequence ( + id INT DEFAULT NEXTVAL(seq_basic) PRIMARY KEY, + name VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_basic_sequence.frm +DROP TABLE t_basic_sequence; +DROP SEQUENCE seq_basic; + +# Test sequence with custom increment and start values +CREATE SEQUENCE seq_custom START WITH 5000 INCREMENT BY 10 MINVALUE 1000 MAXVALUE 999999; +CREATE TABLE t_custom_sequence ( + order_id INT DEFAULT NEXTVAL(seq_custom), + batch_id INT DEFAULT NEXTVAL(seq_custom), + item_code VARCHAR(50), + quantity INT DEFAULT 1 +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_custom_sequence.frm +DROP TABLE t_custom_sequence; +DROP SEQUENCE seq_custom; + +# Test multiple sequences in same table +CREATE SEQUENCE seq_orders START WITH 10000 INCREMENT BY 1; +CREATE SEQUENCE seq_items START WITH 50000 INCREMENT BY 5; +CREATE SEQUENCE seq_batches START WITH 1 INCREMENT BY 1; +CREATE TABLE t_multiple_sequences ( + order_id INT DEFAULT NEXTVAL(seq_orders), + item_id INT DEFAULT NEXTVAL(seq_items), + batch_id INT DEFAULT NEXTVAL(seq_batches), + description TEXT, + price DECIMAL(10,2) DEFAULT 0.00 +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_multiple_sequences.frm +DROP TABLE t_multiple_sequences; +DROP SEQUENCE seq_orders; +DROP SEQUENCE seq_items; +DROP SEQUENCE seq_batches; + +# Test sequence in computed expressions +CREATE SEQUENCE seq_computed START WITH 100 INCREMENT BY 2; +CREATE TABLE t_computed_sequence ( + id INT, + base_number INT DEFAULT NEXTVAL(seq_computed), + doubled_value INT AS (base_number * 2) VIRTUAL, + formatted_id VARCHAR(20) AS (CONCAT('ID-', LPAD(base_number, 6, '0'))) STORED, + is_even BOOLEAN AS (base_number % 2 = 0) VIRTUAL, + range_category VARCHAR(20) AS ( + CASE + WHEN base_number < 200 THEN 'LOW' + WHEN base_number < 500 THEN 'MEDIUM' + ELSE 'HIGH' + END + ) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_computed_sequence.frm +DROP TABLE t_computed_sequence; +DROP SEQUENCE seq_computed; + +# Test sequence with cycle option +CREATE SEQUENCE seq_cycle START WITH 1 INCREMENT BY 1 MAXVALUE 5 CYCLE; +CREATE TABLE t_cycle_sequence ( + id INT AUTO_INCREMENT PRIMARY KEY, + cycle_value INT DEFAULT NEXTVAL(seq_cycle), + data VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_cycle_sequence.frm +DROP TABLE t_cycle_sequence; +DROP SEQUENCE seq_cycle; + +# Test sequence with cache option +CREATE SEQUENCE seq_cached START WITH 1000 INCREMENT BY 1 CACHE 20; +CREATE TABLE t_cached_sequence ( + transaction_id INT DEFAULT NEXTVAL(seq_cached), + reference_id INT DEFAULT NEXTVAL(seq_cached), + amount DECIMAL(15,2), + currency VARCHAR(3) DEFAULT 'USD' +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_cached_sequence.frm +DROP TABLE t_cached_sequence; +DROP SEQUENCE seq_cached; + +# Test sequence combined with other default expressions +CREATE SEQUENCE seq_mixed START WITH 1 INCREMENT BY 1; +CREATE TABLE t_mixed_sequence_defaults ( + id INT DEFAULT NEXTVAL(seq_mixed), + uuid_val VARCHAR(36) DEFAULT (UUID()), + random_val INT DEFAULT (FLOOR(RAND() * 1000)), + timestamp_val TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + computed_val INT AS (id * 100 + random_val) STORED, + formatted_key VARCHAR(50) AS (CONCAT('KEY-', id, '-', SUBSTRING(uuid_val, 1, 8))) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_mixed_sequence_defaults.frm +DROP TABLE t_mixed_sequence_defaults; +DROP SEQUENCE seq_mixed; + +# Test sequences with different data types and constraints +CREATE SEQUENCE seq_bigint START WITH 9223372036854775800 INCREMENT BY 1; +CREATE TABLE t_sequence_datatypes ( + tiny_seq TINYINT DEFAULT (NEXTVAL(seq_bigint) % 127), + small_seq SMALLINT DEFAULT (NEXTVAL(seq_bigint) % 32767), + int_seq INT DEFAULT (NEXTVAL(seq_bigint) % 2147483647), + bigint_seq BIGINT DEFAULT NEXTVAL(seq_bigint), + decimal_seq DECIMAL(20,2) DEFAULT (NEXTVAL(seq_bigint) / 100.0), + float_seq FLOAT DEFAULT (NEXTVAL(seq_bigint) * 0.001) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_sequence_datatypes.frm +DROP TABLE t_sequence_datatypes; +DROP SEQUENCE seq_bigint; + +# Test sequence in conditional expressions +CREATE SEQUENCE seq_conditional START WITH 1 INCREMENT BY 1; +CREATE TABLE t_conditional_sequence ( + id INT, + priority INT DEFAULT ( + CASE + WHEN NEXTVAL(seq_conditional) % 10 = 0 THEN 3 + WHEN NEXTVAL(seq_conditional) % 5 = 0 THEN 2 + ELSE 1 + END + ), + sequence_val INT DEFAULT NEXTVAL(seq_conditional), + status VARCHAR(20) DEFAULT ( + CASE + WHEN sequence_val % 3 = 0 THEN 'APPROVED' + WHEN sequence_val % 3 = 1 THEN 'PENDING' + ELSE 'REJECTED' + END + ) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_conditional_sequence.frm +DROP TABLE t_conditional_sequence; +DROP SEQUENCE seq_conditional; + +# Test sequence with mathematical operations +CREATE SEQUENCE seq_math START WITH 10 INCREMENT BY 3; +CREATE TABLE t_sequence_math ( + base_val INT DEFAULT NEXTVAL(seq_math), + squared_val BIGINT AS (base_val * base_val) VIRTUAL, + cubed_val BIGINT AS (base_val * base_val * base_val) STORED, + factorial_approx DOUBLE AS ( + CASE + WHEN base_val <= 1 THEN 1 + WHEN base_val <= 10 THEN POWER(base_val, base_val/2) + ELSE POWER(base_val, LOG(base_val)) + END + ) VIRTUAL, + fibonacci_approx INT AS ( + CASE + WHEN base_val <= 2 THEN 1 + ELSE FLOOR(POWER(((1 + SQRT(5))/2), base_val) / SQRT(5) + 0.5) + END + ) VIRTUAL, + prime_check BOOLEAN AS ( + base_val > 1 AND ( + base_val = 2 OR base_val = 3 OR base_val = 5 OR base_val = 7 OR + (base_val % 2 != 0 AND base_val % 3 != 0 AND base_val % 5 != 0 AND base_val % 7 != 0) + ) + ) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_sequence_math.frm +DROP TABLE t_sequence_math; +DROP SEQUENCE seq_math; + +# Test sequences with string operations +CREATE SEQUENCE seq_string START WITH 1 INCREMENT BY 1; +CREATE TABLE t_sequence_string ( + id INT DEFAULT NEXTVAL(seq_string), + padded_id VARCHAR(10) AS (LPAD(id, 8, '0')) VIRTUAL, + hex_id VARCHAR(20) AS (CONCAT('0x', HEX(id))) VIRTUAL, + binary_id VARCHAR(64) AS (BIN(id)) VIRTUAL, + encoded_id VARCHAR(50) AS (TO_BASE64(CONCAT('ID:', id))) STORED, + checksum_id VARCHAR(50) AS (SHA2(CONCAT('prefix:', id, ':suffix'), 256)) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_sequence_string.frm +DROP TABLE t_sequence_string; +DROP SEQUENCE seq_string; + +# Test sequence with date/time operations +CREATE SEQUENCE seq_datetime START WITH 1 INCREMENT BY 1; +CREATE TABLE t_sequence_datetime ( + seq_id INT DEFAULT NEXTVAL(seq_datetime), + base_date DATE DEFAULT CURRENT_DATE, + computed_date DATE AS (DATE_ADD(base_date, INTERVAL seq_id DAY)) VIRTUAL, + computed_timestamp DATETIME AS (DATE_ADD(NOW(), INTERVAL seq_id MINUTE)) VIRTUAL, + week_offset DATE AS (DATE_ADD(base_date, INTERVAL seq_id WEEK)) STORED, + year_offset YEAR AS (YEAR(DATE_ADD(base_date, INTERVAL seq_id YEAR))) VIRTUAL, + time_based VARCHAR(20) AS ( + CASE (seq_id % 4) + WHEN 0 THEN 'MORNING' + WHEN 1 THEN 'AFTERNOON' + WHEN 2 THEN 'EVENING' + ELSE 'NIGHT' + END + ) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_sequence_datetime.frm +DROP TABLE t_sequence_datetime; +DROP SEQUENCE seq_datetime; + +# Test sequence with complex nested expressions +CREATE SEQUENCE seq_nested START WITH 1 INCREMENT BY 1; +CREATE TABLE t_nested_sequence_expressions ( + primary_seq INT DEFAULT NEXTVAL(seq_nested), + nested_calc INT AS ( + CASE + WHEN primary_seq <= 10 THEN primary_seq * 2 + WHEN primary_seq <= 100 THEN ( + CASE + WHEN primary_seq % 10 = 0 THEN primary_seq / 10 + ELSE primary_seq + END + ) + ELSE FLOOR(SQRT(primary_seq)) + END + ) STORED, + multi_level_check BOOLEAN AS ( + (primary_seq > 0) AND + (nested_calc > 0) AND + (CASE + WHEN primary_seq < 50 THEN (nested_calc < primary_seq) + ELSE (nested_calc > primary_seq) + END) + ) VIRTUAL, + formatted_result VARCHAR(100) AS ( + CONCAT( + 'SEQ:', primary_seq, + '|CALC:', nested_calc, + '|RATIO:', ROUND(nested_calc/primary_seq, 2), + '|VALID:', IF(multi_level_check, 'YES', 'NO') + ) + ) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_nested_sequence_expressions.frm +DROP TABLE t_nested_sequence_expressions; +DROP SEQUENCE seq_nested; + +# Test sequences with JSON integration +CREATE SEQUENCE seq_json START WITH 1 INCREMENT BY 1 MAXVALUE 1000; +CREATE TABLE t_sequence_json ( + id INT DEFAULT NEXTVAL(seq_json), + metadata JSON DEFAULT (JSON_OBJECT( + 'seq_id', NEXTVAL(seq_json), + 'created_at', NOW(), + 'version', 1 + )), + extracted_seq INT AS (JSON_EXTRACT(metadata, '$.seq_id')) VIRTUAL, + seq_diff INT AS (id - JSON_EXTRACT(metadata, '$.seq_id')) STORED, + enhanced_metadata JSON AS ( + JSON_SET( + metadata, + '$.computed.id_sum', id + JSON_EXTRACT(metadata, '$.seq_id'), + '$.computed.id_product', id * JSON_EXTRACT(metadata, '$.seq_id'), + '$.computed.relationship', + CASE + WHEN id > JSON_EXTRACT(metadata, '$.seq_id') THEN 'GREATER' + WHEN id < JSON_EXTRACT(metadata, '$.seq_id') THEN 'LESS' + ELSE 'EQUAL' + END + ) + ) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_sequence_json.frm +DROP TABLE t_sequence_json; +DROP SEQUENCE seq_json; + + +# Test sequences with negative increments +CREATE SEQUENCE seq_negative START WITH 1000 INCREMENT BY -1 MINVALUE 1 MAXVALUE 1000; +CREATE TABLE t_negative_sequence ( + countdown INT DEFAULT NEXTVAL(seq_negative), + remaining_pct DECIMAL(5,2) AS ((countdown / 1000.0) * 100) VIRTUAL, + status_level VARCHAR(10) AS ( + CASE + WHEN countdown > 800 THEN 'HIGH' + WHEN countdown > 500 THEN 'MEDIUM' + WHEN countdown > 200 THEN 'LOW' + ELSE 'CRITICAL' + END + ) STORED, + is_milestone BOOLEAN AS (countdown % 100 = 0) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_negative_sequence.frm +DROP TABLE t_negative_sequence; +DROP SEQUENCE seq_negative; + +# Test edge case: sequence with complex mathematical functions +CREATE SEQUENCE seq_complex_math START WITH 1 INCREMENT BY 1; +CREATE TABLE t_complex_sequence_math ( + n INT DEFAULT NEXTVAL(seq_complex_math), + harmonic_series DOUBLE AS (1.0/n) VIRTUAL, + geometric_series DOUBLE AS (POWER(0.5, n)) VIRTUAL, + log_series DOUBLE AS (CASE WHEN n > 0 THEN LOG(n) ELSE NULL END) VIRTUAL, + trig_approx DOUBLE AS (SIN(n * PI()/180)) STORED, + exponential_decay DOUBLE AS (EXP(-n/10.0)) VIRTUAL, + polynomial_val DOUBLE AS (n*n*n - 3*n*n + 2*n + 1) VIRTUAL, + modular_arithmetic INT AS ((n * n + 7*n + 12) % 1000) VIRTUAL +); +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_complex_sequence_math.frm +DROP TABLE t_complex_sequence_math; +DROP SEQUENCE seq_complex_math; + +--echo Sequence default expressions test completed diff --git a/mysql-test/suite/client/mariadb-frm-simple.result b/mysql-test/suite/client/mariadb-frm-simple.result new file mode 100644 index 0000000000000..0375467ef367d --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-simple.result @@ -0,0 +1,26 @@ +MariaDB frm parser test #1 +simple file +CREATE TABLE `table_simple` ( + `id` int(11) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci +CREATE TABLE t_comments_with_indexes ( +id INT COMMENT 'Primary key with index', +name VARCHAR(100) COMMENT 'Name field with regular index', +email VARCHAR(255) COMMENT 'Email with unique constraint', +category VARCHAR(50) COMMENT 'Category for grouping', +content TEXT COMMENT 'Full-text searchable content', +PRIMARY KEY (id), +INDEX idx_name (name) COMMENT 'Index for name lookups', +UNIQUE KEY idx_email (email) COMMENT 'Unique constraint on email', +INDEX idx_category (category) COMMENT 'Category grouping index', +FULLTEXT KEY ft_content (content) COMMENT 'Full-text search index' +) COMMENT = 'Table demonstrating comments on both fields and indexes'; +DROP TABLE t_comments_with_indexes; +CREATE TABLE t_comments_vcols ( +first_name VARCHAR(50) COMMENT 'User first name', +last_name VARCHAR(50) COMMENT 'User last name', +full_name VARCHAR(101) AS (CONCAT(first_name, ' ', last_name)) COMMENT 'Computed full name', +name_length INT AS (LENGTH(CONCAT(first_name, last_name))) STORED COMMENT 'Stored computed name length' +) COMMENT = 'Table with comments on virtual/generated columns'; +DROP TABLE t_comments_vcols; +Tests completed diff --git a/mysql-test/suite/client/mariadb-frm-simple.test b/mysql-test/suite/client/mariadb-frm-simple.test new file mode 100644 index 0000000000000..a52b020a6b09f --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-simple.test @@ -0,0 +1,48 @@ +--echo MariaDB frm parser test #1 +--echo simple file +--let $MYSQLD_DATADIR= `select @@datadir` + +# Skip test if WSREP is active (mariadb-frm tool is incompatible with WSREP) +if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +} + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} + +--exec $MARIADB_FRM $MYSQL_TEST_DIR/std_data/frm/table_simple.frm + +# Test comments with indexes +CREATE TABLE t_comments_with_indexes ( + id INT COMMENT 'Primary key with index', + name VARCHAR(100) COMMENT 'Name field with regular index', + email VARCHAR(255) COMMENT 'Email with unique constraint', + category VARCHAR(50) COMMENT 'Category for grouping', + content TEXT COMMENT 'Full-text searchable content', + PRIMARY KEY (id), + INDEX idx_name (name) COMMENT 'Index for name lookups', + UNIQUE KEY idx_email (email) COMMENT 'Unique constraint on email', + INDEX idx_category (category) COMMENT 'Category grouping index', + FULLTEXT KEY ft_content (content) COMMENT 'Full-text search index' +) COMMENT = 'Table demonstrating comments on both fields and indexes'; +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_comments_with_indexes.frm +DROP TABLE t_comments_with_indexes; + + +# Test comments with virtual columns +CREATE TABLE t_comments_vcols ( + first_name VARCHAR(50) COMMENT 'User first name', + last_name VARCHAR(50) COMMENT 'User last name', + full_name VARCHAR(101) AS (CONCAT(first_name, ' ', last_name)) COMMENT 'Computed full name', + name_length INT AS (LENGTH(CONCAT(first_name, last_name))) STORED COMMENT 'Stored computed name length' +) COMMENT = 'Table with comments on virtual/generated columns'; +--error 5 +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_comments_vcols.frm +DROP TABLE t_comments_vcols; + +--echo Tests completed diff --git a/mysql-test/suite/client/mariadb-frm-vcols.result b/mysql-test/suite/client/mariadb-frm-vcols.result new file mode 100644 index 0000000000000..a057962356ca6 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-vcols.result @@ -0,0 +1,141 @@ +MariaDB frm parser test - Virtual Columns +Testing virtual/generated columns with various expressions +CREATE TABLE t_basic_vcols ( +a INT, +b INT, +sum_col INT AS (a + b) VIRTUAL, +product_col INT AS (a * b) STORED, +difference_col INT AS (a - b) VIRTUAL +); +CREATE TABLE `t_basic_vcols` ( + `a` int(11) DEFAULT NULL, + `b` int(11) DEFAULT NULL, + `sum_col` int(11) GENERATED ALWAYS AS (`a` + `b`) VIRTUAL, + `product_col` int(11) GENERATED ALWAYS AS (`a` * `b`) STORED, + `difference_col` int(11) GENERATED ALWAYS AS (`a` - `b`) VIRTUAL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_basic_vcols; +CREATE TABLE t_string_vcols ( +first_name VARCHAR(50), +last_name VARCHAR(50), +full_name VARCHAR(101) AS (CONCAT(first_name, ' ', last_name)) VIRTUAL, +initials CHAR(2) AS (CONCAT(LEFT(first_name, 1), LEFT(last_name, 1))) STORED, +name_length INT AS (LENGTH(CONCAT(first_name, last_name))) VIRTUAL +); +CREATE TABLE `t_string_vcols` ( + `first_name` varchar(50) DEFAULT NULL, + `last_name` varchar(50) DEFAULT NULL, + `full_name` varchar(101) GENERATED ALWAYS AS (concat(`first_name`,' ',`last_name`)) VIRTUAL, + `initials` char(2) GENERATED ALWAYS AS (concat(left(`first_name`,1),left(`last_name`,1))) STORED, + `name_length` int(11) GENERATED ALWAYS AS (octet_length(concat(`first_name`,`last_name`))) VIRTUAL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_string_vcols; +CREATE TABLE t_datetime_vcols ( +birth_date DATE, +created_at DATETIME, +birth_year INT AS (YEAR(birth_date)) STORED, +created_date DATE AS (DATE(created_at)) VIRTUAL, +day_of_week VARCHAR(10) AS (DAYNAME(birth_date)) VIRTUAL +); +CREATE TABLE `t_datetime_vcols` ( + `birth_date` date DEFAULT NULL, + `created_at` datetime DEFAULT NULL, + `birth_year` int(11) GENERATED ALWAYS AS (year(`birth_date`)) STORED, + `created_date` date GENERATED ALWAYS AS (cast(`created_at` as date)) VIRTUAL, + `day_of_week` varchar(10) GENERATED ALWAYS AS (dayname(`birth_date`)) VIRTUAL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_datetime_vcols; +CREATE TABLE t_math_vcols ( +radius DECIMAL(10,2), +height DECIMAL(10,2), +area DECIMAL(15,4) AS (PI() * radius * radius) VIRTUAL, +volume DECIMAL(20,6) AS (PI() * radius * radius * height) STORED, +circumference DECIMAL(15,4) AS (2 * PI() * radius) VIRTUAL, +sqrt_area DECIMAL(10,4) AS (SQRT(PI() * radius * radius)) VIRTUAL +); +CREATE TABLE `t_math_vcols` ( + `radius` decimal(10,2) DEFAULT NULL, + `height` decimal(10,2) DEFAULT NULL, + `area` decimal(15,4) GENERATED ALWAYS AS (pi() * `radius` * `radius`) VIRTUAL, + `volume` decimal(20,6) GENERATED ALWAYS AS (pi() * `radius` * `radius` * `height`) STORED, + `circumference` decimal(15,4) GENERATED ALWAYS AS (2 * pi() * `radius`) VIRTUAL, + `sqrt_area` decimal(10,4) GENERATED ALWAYS AS (sqrt(pi() * `radius` * `radius`)) VIRTUAL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_math_vcols; +CREATE TABLE t_conditional_vcols ( +score INT, +status VARCHAR(20), +grade CHAR(1) AS (CASE +WHEN score >= 90 THEN 'A' + WHEN score >= 80 THEN 'B' +WHEN score >= 70 THEN 'C' + WHEN score >= 60 THEN 'D' + ELSE 'F' + END) VIRTUAL, +is_active BOOLEAN AS (status = 'active') STORED, +pass_fail VARCHAR(4) AS (IF(score >= 60, 'PASS', 'FAIL')) VIRTUAL +); +CREATE TABLE `t_conditional_vcols` ( + `score` int(11) DEFAULT NULL, + `status` varchar(20) DEFAULT NULL, + `grade` char(1) GENERATED ALWAYS AS (case when `score` >= 90 then 'A' when `score` >= 80 then 'B' when `score` >= 70 then 'C' when `score` >= 60 then 'D' else 'F' end) VIRTUAL, + `is_active` tinyint(1) GENERATED ALWAYS AS (`status` = 'active') STORED, + `pass_fail` varchar(4) GENERATED ALWAYS AS (if(`score` >= 60,'PASS','FAIL')) VIRTUAL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_conditional_vcols; +CREATE TABLE t_json_vcols ( +id INT, +user_data JSON, +user_name VARCHAR(100) AS (JSON_UNQUOTE(JSON_EXTRACT(user_data, '$.name'))) VIRTUAL, +user_age INT AS (JSON_EXTRACT(user_data, '$.age')) STORED, +has_email BOOLEAN AS (JSON_CONTAINS_PATH(user_data, 'one', '$.email')) VIRTUAL +); +CREATE TABLE `t_json_vcols` ( + `id` int(11) DEFAULT NULL, + `user_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`user_data`)), + `user_name` varchar(100) GENERATED ALWAYS AS (json_unquote(json_extract(`user_data`,'$.name'))) VIRTUAL, + `user_age` int(11) GENERATED ALWAYS AS (json_extract(`user_data`,'$.age')) STORED, + `has_email` tinyint(1) GENERATED ALWAYS AS (json_contains_path(`user_data`,'one','$.email')) VIRTUAL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_json_vcols; +CREATE TABLE t_vcols_with_indexes ( +id INT, +first_name VARCHAR(50), +last_name VARCHAR(50), +full_name VARCHAR(101) AS (CONCAT(first_name, ' ', last_name)) STORED, +name_upper VARCHAR(101) AS (UPPER(CONCAT(first_name, ' ', last_name))) VIRTUAL, +PRIMARY KEY (id), +INDEX idx_full_name (full_name), +INDEX idx_last_name (last_name) +); +CREATE TABLE `t_vcols_with_indexes` ( + `id` int(11) NOT NULL, + `first_name` varchar(50) DEFAULT NULL, + `last_name` varchar(50) DEFAULT NULL, + `full_name` varchar(101) GENERATED ALWAYS AS (concat(`first_name`,' ',`last_name`)) STORED, + `name_upper` varchar(101) GENERATED ALWAYS AS (ucase(concat(`first_name`,' ',`last_name`))) VIRTUAL, + PRIMARY KEY (`id`), + KEY `idx_full_name` (`full_name`), + KEY `idx_last_name` (`last_name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_vcols_with_indexes; +CREATE TABLE t_complex_vcols ( +x DECIMAL(10,2), +y DECIMAL(10,2), +z DECIMAL(10,2), +distance_2d DECIMAL(15,6) AS (SQRT(x*x + y*y)) VIRTUAL, +distance_3d DECIMAL(15,6) AS (SQRT(x*x + y*y + z*z)) STORED, +normalized_x DECIMAL(10,6) AS (x / SQRT(x*x + y*y + z*z)) VIRTUAL, +is_unit_vector BOOLEAN AS (ABS(SQRT(x*x + y*y + z*z) - 1.0) < 0.001) VIRTUAL +); +CREATE TABLE `t_complex_vcols` ( + `x` decimal(10,2) DEFAULT NULL, + `y` decimal(10,2) DEFAULT NULL, + `z` decimal(10,2) DEFAULT NULL, + `distance_2d` decimal(15,6) GENERATED ALWAYS AS (sqrt(`x` * `x` + `y` * `y`)) VIRTUAL, + `distance_3d` decimal(15,6) GENERATED ALWAYS AS (sqrt(`x` * `x` + `y` * `y` + `z` * `z`)) STORED, + `normalized_x` decimal(10,6) GENERATED ALWAYS AS (`x` / sqrt(`x` * `x` + `y` * `y` + `z` * `z`)) VIRTUAL, + `is_unit_vector` tinyint(1) GENERATED ALWAYS AS (abs(sqrt(`x` * `x` + `y` * `y` + `z` * `z`) - 1.0) < 0.001) VIRTUAL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_complex_vcols; +Virtual columns test completed diff --git a/mysql-test/suite/client/mariadb-frm-vcols.test b/mysql-test/suite/client/mariadb-frm-vcols.test new file mode 100644 index 0000000000000..dc25c99294174 --- /dev/null +++ b/mysql-test/suite/client/mariadb-frm-vcols.test @@ -0,0 +1,117 @@ +--echo MariaDB frm parser test - Virtual Columns +--echo Testing virtual/generated columns with various expressions + +# Initialize the MYSQLD_DATADIR variable +--let $MYSQLD_DATADIR= `select @@datadir` +# if (`SELECT COUNT(*)>0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'wsrep' AND PLUGIN_STATUS='ACTIVE'`) +# { +# --skip Test requires wsrep plugin to be inactive (mariadb-frm tool incompatible with WSREP) +# } + +# Skip test if running on embedded server (mariadb-frm tool is incompatible with embedded server) +if (`SELECT VERSION() LIKE '%embedded%'`) +{ + --skip Test requires non-embedded server (mariadb-frm tool incompatible with embedded server) +} + +# Test basic virtual columns +CREATE TABLE t_basic_vcols ( + a INT, + b INT, + sum_col INT AS (a + b) VIRTUAL, + product_col INT AS (a * b) STORED, + difference_col INT AS (a - b) VIRTUAL +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_basic_vcols.frm +DROP TABLE t_basic_vcols; + +# Test string virtual columns +CREATE TABLE t_string_vcols ( + first_name VARCHAR(50), + last_name VARCHAR(50), + full_name VARCHAR(101) AS (CONCAT(first_name, ' ', last_name)) VIRTUAL, + initials CHAR(2) AS (CONCAT(LEFT(first_name, 1), LEFT(last_name, 1))) STORED, + name_length INT AS (LENGTH(CONCAT(first_name, last_name))) VIRTUAL +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_string_vcols.frm +DROP TABLE t_string_vcols; + +# Test date/time virtual columns +CREATE TABLE t_datetime_vcols ( + birth_date DATE, + created_at DATETIME, + birth_year INT AS (YEAR(birth_date)) STORED, + created_date DATE AS (DATE(created_at)) VIRTUAL, + day_of_week VARCHAR(10) AS (DAYNAME(birth_date)) VIRTUAL +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_datetime_vcols.frm +DROP TABLE t_datetime_vcols; + +# Test mathematical expressions +CREATE TABLE t_math_vcols ( + radius DECIMAL(10,2), + height DECIMAL(10,2), + area DECIMAL(15,4) AS (PI() * radius * radius) VIRTUAL, + volume DECIMAL(20,6) AS (PI() * radius * radius * height) STORED, + circumference DECIMAL(15,4) AS (2 * PI() * radius) VIRTUAL, + sqrt_area DECIMAL(10,4) AS (SQRT(PI() * radius * radius)) VIRTUAL +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_math_vcols.frm +DROP TABLE t_math_vcols; + +# Test conditional expressions +CREATE TABLE t_conditional_vcols ( + score INT, + status VARCHAR(20), + grade CHAR(1) AS (CASE + WHEN score >= 90 THEN 'A' + WHEN score >= 80 THEN 'B' + WHEN score >= 70 THEN 'C' + WHEN score >= 60 THEN 'D' + ELSE 'F' + END) VIRTUAL, + is_active BOOLEAN AS (status = 'active') STORED, + pass_fail VARCHAR(4) AS (IF(score >= 60, 'PASS', 'FAIL')) VIRTUAL +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_conditional_vcols.frm +DROP TABLE t_conditional_vcols; + +# Test JSON virtual columns (if JSON supported) +CREATE TABLE t_json_vcols ( + id INT, + user_data JSON, + user_name VARCHAR(100) AS (JSON_UNQUOTE(JSON_EXTRACT(user_data, '$.name'))) VIRTUAL, + user_age INT AS (JSON_EXTRACT(user_data, '$.age')) STORED, + has_email BOOLEAN AS (JSON_CONTAINS_PATH(user_data, 'one', '$.email')) VIRTUAL +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_json_vcols.frm +DROP TABLE t_json_vcols; + +# Test virtual columns with indexes +CREATE TABLE t_vcols_with_indexes ( + id INT, + first_name VARCHAR(50), + last_name VARCHAR(50), + full_name VARCHAR(101) AS (CONCAT(first_name, ' ', last_name)) STORED, + name_upper VARCHAR(101) AS (UPPER(CONCAT(first_name, ' ', last_name))) VIRTUAL, + PRIMARY KEY (id), + INDEX idx_full_name (full_name), + INDEX idx_last_name (last_name) +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_vcols_with_indexes.frm +DROP TABLE t_vcols_with_indexes; + +# Test complex nested expressions +CREATE TABLE t_complex_vcols ( + x DECIMAL(10,2), + y DECIMAL(10,2), + z DECIMAL(10,2), + distance_2d DECIMAL(15,6) AS (SQRT(x*x + y*y)) VIRTUAL, + distance_3d DECIMAL(15,6) AS (SQRT(x*x + y*y + z*z)) STORED, + normalized_x DECIMAL(10,6) AS (x / SQRT(x*x + y*y + z*z)) VIRTUAL, + is_unit_vector BOOLEAN AS (ABS(SQRT(x*x + y*y + z*z) - 1.0) < 0.001) VIRTUAL +); +--exec $MARIADB_FRM $MYSQLD_DATADIR/test/t_complex_vcols.frm +DROP TABLE t_complex_vcols; + +--echo Virtual columns test completed diff --git a/sql/create_options.cc b/sql/create_options.cc index 718eee4d458c9..4dc2b8f86748d 100644 --- a/sql/create_options.cc +++ b/sql/create_options.cc @@ -486,8 +486,13 @@ void free_sysvar_table_options(ha_create_table_option *rules) @retval FALSE OK */ +bool (*parse_engine_table_options_hook)(THD *thd, handlerton *ht, TABLE_SHARE *share) = nullptr; +bool (*engine_table_options_frm_read_hook)(const uchar *buff, size_t length, TABLE_SHARE *share) = nullptr; + bool parse_engine_table_options(THD *thd, handlerton *ht, TABLE_SHARE *share) { + if (parse_engine_table_options_hook) + return parse_engine_table_options_hook(thd, ht, share); MEM_ROOT *root= &share->mem_root; DBUG_ENTER("parse_engine_table_options"); @@ -789,6 +794,8 @@ uchar *engine_option_value::frm_read(const uchar *buff, const uchar *buff_end, bool engine_table_options_frm_read(const uchar *buff, size_t length, TABLE_SHARE *share) { + if (engine_table_options_frm_read_hook) + return engine_table_options_frm_read_hook(buff, length, share); const uchar *buff_end= buff + length; engine_option_value *UNINIT_VAR(end); MEM_ROOT *root= &share->mem_root; diff --git a/sql/create_options.h b/sql/create_options.h index d946a29fc1f3e..6fcebcfcbb89a 100644 --- a/sql/create_options.h +++ b/sql/create_options.h @@ -107,6 +107,9 @@ class Create_field; bool resolve_sysvar_table_options(ha_create_table_option *rules); void free_sysvar_table_options(ha_create_table_option *rules); +extern bool (*parse_engine_table_options_hook)(THD *thd, handlerton *ht, TABLE_SHARE *share); +extern bool (*engine_table_options_frm_read_hook)(const uchar *buff, size_t length, TABLE_SHARE *share); + bool parse_engine_table_options(THD *thd, handlerton *ht, TABLE_SHARE *share); #ifdef WITH_PARTITION_STORAGE_ENGINE bool parse_engine_part_options(THD *thd, TABLE *table); diff --git a/sql/handler.cc b/sql/handler.cc index 12dc4240c63fb..ac50ab3cf1bf6 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -203,6 +203,11 @@ static int commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans); +handlerton *(*ha_default_handlerton_hook)(THD *thd) = nullptr; +plugin_ref (*ha_resolve_by_name_hook)(THD *thd, const LEX_CSTRING *name, bool is_temp_table) = nullptr; +plugin_ref (*ha_lock_engine_hook)(THD *thd, const handlerton *hton) = nullptr; +handler *(*get_new_handler_hook)(TABLE_SHARE *share, MEM_ROOT *alloc, handlerton *db_type) = nullptr; + static plugin_ref ha_default_plugin(THD *thd) { if (thd->variables.table_plugin) @@ -232,6 +237,8 @@ static plugin_ref ha_default_tmp_plugin(THD *thd) */ handlerton *ha_default_handlerton(THD *thd) { + if (ha_default_handlerton_hook) + return ha_default_handlerton_hook(thd); plugin_ref plugin= ha_default_plugin(thd); DBUG_ASSERT(plugin); handlerton *hton= plugin_hton(plugin); @@ -264,6 +271,8 @@ handlerton *ha_default_tmp_handlerton(THD *thd) plugin_ref ha_resolve_by_name(THD *thd, const LEX_CSTRING *name, bool tmp_table) { + if (ha_resolve_by_name_hook) + return ha_resolve_by_name_hook(thd, name, tmp_table); plugin_ref plugin; redo: @@ -337,6 +346,8 @@ Storage_engine_name::resolve_storage_engine_with_error(THD *thd, plugin_ref ha_lock_engine(THD *thd, const handlerton *hton) { + if (ha_lock_engine_hook) + return ha_lock_engine_hook(thd, hton); if (hton) { st_plugin_int *plugin= hton2plugin[hton->slot]; @@ -381,6 +392,8 @@ handlerton *ha_checktype(THD *thd, handlerton *hton, bool no_substitute) handler *get_new_handler(TABLE_SHARE *share, MEM_ROOT *alloc, handlerton *db_type) { + if (get_new_handler_hook) + return get_new_handler_hook(share, alloc, db_type); handler *file; DBUG_ENTER("get_new_handler"); DBUG_PRINT("enter", ("alloc: %p", alloc)); diff --git a/sql/handler.h b/sql/handler.h index f0763aebe717c..951f992f4cd80 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1917,6 +1917,11 @@ static inline handlerton *plugin_hton(plugin_ref plugin) return plugin_data(plugin, handlerton *); } +extern handlerton *(*ha_default_handlerton_hook)(THD *thd); +extern plugin_ref (*ha_resolve_by_name_hook)(THD *thd, const LEX_CSTRING *name, bool is_temp_table); +extern plugin_ref (*ha_lock_engine_hook)(THD *thd, const handlerton *hton); +extern handler *(*get_new_handler_hook)(TABLE_SHARE *share, MEM_ROOT *alloc, handlerton *db_type); + handlerton *ha_default_handlerton(THD *thd); handlerton *ha_default_tmp_handlerton(THD *thd); diff --git a/sql/item.h b/sql/item.h index e0c398daa4225..abc24483bb083 100644 --- a/sql/item.h +++ b/sql/item.h @@ -3232,6 +3232,77 @@ class Item_basic_constant :public Item_basic_value }; +/** + Frm_parser_item - placeholder Item for FRM parser mode. + + Stores a raw expression string without parsing or evaluating it. + The standalone mariadb-frm tool uses this to represent virtual column + expressions so the full SQL parser is not invoked. +*/ +class Frm_parser_item: public Item +{ + String *expr_str; + uint expr_length; + +protected: + Item *shallow_copy(THD *thd) const override + { + return new(get_thd_memroot(thd)) Frm_parser_item(thd, expr_str->ptr(), expr_length); + } + + Item *deep_copy(THD *thd) const override + { + return shallow_copy(thd); + } + +public: + Frm_parser_item(THD *thd, const char *expr, uint length) : Item(thd) + { + expr_str= new String(); + expr_str->copy(expr, length, &my_charset_utf8mb3_general_ci); + expr_length= length; + } + + enum Type type() const override { return CONST_ITEM; } + + void print(String *str, enum_query_type query_type) override + { + if (!expr_str || expr_str->length() == 0) + return; + + const char *prefix= "PARSE_VCOL_EXPR "; + const uint prefix_len= 16; + + if (expr_str->length() > prefix_len && + memcmp(expr_str->ptr(), prefix, prefix_len) == 0) + str->append(expr_str->ptr() + prefix_len, + expr_str->length() - prefix_len); + else + str->append(expr_str->ptr(), expr_str->length()); + } + + const char *full_name() { return "frm_parser_expression"; } + + double val_real() override { return 0.0; } + longlong val_int() override { return 0; } + String *val_str(String *) override { return expr_str; } + void fix_length_and_dec() {} + + const Type_handler *type_handler() const override { return &type_handler_varchar; } + my_decimal *val_decimal(my_decimal *) override { return NULL; } + bool get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) override + { + bzero((char*) ltime, sizeof(*ltime)); + return 1; + } + Field *create_tmp_field_ex(MEM_ROOT *root, TABLE *table, Tmp_field_src *src, + const Tmp_field_param *param) override + { + return tmp_table_field_from_field_type_maybe_null(root, table, src, param, false); + } +}; + + /***************************************************************************** The class is a base class for representation of stored routine variables in the Item-hierarchy. There are the following kinds of SP-vars: diff --git a/sql/log.cc b/sql/log.cc index 387795e30fce7..a0b2351326758 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -9142,9 +9142,13 @@ MYSQL_BIN_LOG::update_gtid_index(uint32 offset, const rpl_gtid *gtid) } } +int (*error_log_print_hook)(enum loglevel level, const char *format, va_list args) = nullptr; + int error_log_print(enum loglevel level, const char *format, va_list args) { + if (error_log_print_hook) + return error_log_print_hook(level, format, args); return logger.error_log_print(level, format, args); } diff --git a/sql/log.h b/sql/log.h index e0d3d37c3bb5b..9db2904ee5698 100644 --- a/sql/log.h +++ b/sql/log.h @@ -1449,6 +1449,8 @@ typedef void (*sql_print_message_func)(const char *format, ...) ATTRIBUTE_FORMAT_FPTR(printf, 1, 2); extern sql_print_message_func sql_print_message_handlers[]; +extern int (*error_log_print_hook)(enum loglevel level, const char *format, va_list args); + int error_log_print(enum loglevel level, const char *format, va_list args) ATTRIBUTE_FORMAT(printf, 2, 0); diff --git a/sql/sql_error.cc b/sql/sql_error.cc index 703b64570bfb7..5ae953bce9d94 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -797,6 +797,9 @@ void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level, } +void (*push_warning_printf_hook)(THD *thd, Sql_condition::enum_warning_level level, + uint code, const char *format, va_list args) = nullptr; + /* This is an overload of push_warning_printf() accepting va_list as a list of format arguments. @@ -805,6 +808,11 @@ void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level, void push_warning_vprintf(THD *thd, Sql_condition::enum_warning_level level, uint code, const char *format, va_list args) { + if (push_warning_printf_hook) + { + push_warning_printf_hook(thd, level, code, format, args); + return; + } char warning[MYSQL_ERRMSG_SIZE]; DBUG_ASSERT(code != 0); diff --git a/sql/sql_error.h b/sql/sql_error.h index a772594bb1b44..940a249fe39ba 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -1331,6 +1331,9 @@ void convert_error_to_warning(THD *thd); void push_warning(THD *thd, Sql_condition::enum_warning_level level, uint code, const char *msg); +extern void (*push_warning_printf_hook)(THD *thd, Sql_condition::enum_warning_level level, + uint code, const char *format, va_list args); + void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level, uint code, const char *format, ...) ATTRIBUTE_FORMAT(printf, 4, 5); diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 7b566ac577017..60ce513a86312 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1028,8 +1028,13 @@ static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref rc, Otherwise, when passing a NULL THD, the caller must arrange that plugin unlock happens later. */ +plugin_ref (*plugin_lock_hook)(THD *thd, plugin_ref ptr) = nullptr; +void (*plugin_unlock_hook)(THD *thd, plugin_ref plugin) = nullptr; + plugin_ref plugin_lock(THD *thd, plugin_ref ptr) { + if (plugin_lock_hook) + return plugin_lock_hook(thd, ptr); LEX *lex= thd ? thd->lex : 0; plugin_ref rc; DBUG_ENTER("plugin_lock"); @@ -1412,6 +1417,11 @@ static void intern_plugin_unlock(LEX *lex, plugin_ref plugin) void plugin_unlock(THD *thd, plugin_ref plugin) { + if (plugin_unlock_hook) + { + plugin_unlock_hook(thd, plugin); + return; + } LEX *lex= thd ? thd->lex : 0; DBUG_ENTER("plugin_unlock"); if (!plugin) diff --git a/sql/sql_plugin.h b/sql/sql_plugin.h index f1650c52deb02..0eae8f112a9e6 100644 --- a/sql/sql_plugin.h +++ b/sql/sql_plugin.h @@ -169,6 +169,9 @@ void add_plugin_options(DYNAMIC_ARRAY *options, MEM_ROOT *mem_root); extern bool plugin_is_ready(const LEX_CSTRING *name, int type); #define my_plugin_lock_by_name(A,B,C) plugin_lock_by_name(A,B,C) #define my_plugin_lock(A,B) plugin_lock(A,B) +extern plugin_ref (*plugin_lock_hook)(THD *thd, plugin_ref ptr); +extern void (*plugin_unlock_hook)(THD *thd, plugin_ref plugin); + extern plugin_ref plugin_lock(THD *thd, plugin_ref ptr); extern plugin_ref plugin_lock_by_name(THD *thd, const LEX_CSTRING *name, int type); diff --git a/sql/table.cc b/sql/table.cc index fb25f4ddaf1bf..9693c2068a371 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -60,6 +60,9 @@ #include "sql_class.h" #include "opt_hints.h" +bool global_frm_parser_mode = false; + + /* For MySQL 5.7 virtual fields */ #define MYSQL57_GENERATED_FIELD 128 #define MYSQL57_GCOL_HEADER_SIZE 4 @@ -92,7 +95,7 @@ struct extra2_fields }; static Virtual_column_info * unpack_vcol_info_from_frm(THD *, - TABLE *, String *, Virtual_column_info **, bool *); + TABLE *, String *, Virtual_column_info **, bool *, bool); /* Lex_ident_db does not have operator""_Lex_ident_db, @@ -1174,7 +1177,7 @@ static void update_vcol_key_covering(Field *vcol_field) expression */ bool parse_vcol_defs(THD *thd, MEM_ROOT *mem_root, TABLE *table, - bool *error_reported, vcol_init_mode mode) + bool *error_reported, vcol_init_mode mode, bool frm_parser_mode) { struct check_vcol_forward_refs { @@ -1270,7 +1273,7 @@ bool parse_vcol_defs(THD *thd, MEM_ROOT *mem_root, TABLE *table, case VCOL_GENERATED_VIRTUAL: case VCOL_GENERATED_STORED: vcol= unpack_vcol_info_from_frm(thd, table, &expr_str, - &((*field_ptr)->vcol_info), error_reported); + &((*field_ptr)->vcol_info), error_reported, frm_parser_mode); *(vfield_ptr++)= *field_ptr; DBUG_ASSERT(table->map == 0); /* @@ -1296,7 +1299,7 @@ bool parse_vcol_defs(THD *thd, MEM_ROOT *mem_root, TABLE *table, case VCOL_DEFAULT: vcol= unpack_vcol_info_from_frm(thd, table, &expr_str, &((*field_ptr)->default_value), - error_reported); + error_reported, frm_parser_mode); if (vcol && field_ptr[0]->check_assignability_from(vcol->expr->type_handler(), false)) @@ -1311,12 +1314,12 @@ bool parse_vcol_defs(THD *thd, MEM_ROOT *mem_root, TABLE *table, case VCOL_CHECK_FIELD: vcol= unpack_vcol_info_from_frm(thd, table, &expr_str, &((*field_ptr)->check_constraint), - error_reported); + error_reported, frm_parser_mode); *check_constraint_ptr++= (*field_ptr)->check_constraint; break; case VCOL_CHECK_TABLE: vcol= unpack_vcol_info_from_frm(thd, table, &expr_str, - check_constraint_ptr, error_reported); + check_constraint_ptr, error_reported, frm_parser_mode); check_constraint_ptr++; break; } @@ -1395,7 +1398,7 @@ bool parse_vcol_defs(THD *thd, MEM_ROOT *mem_root, TABLE *table, expr_str.append(')'); vcol= unpack_vcol_info_from_frm(thd, table, &expr_str, &((*field_ptr)->default_value), - error_reported); + error_reported, frm_parser_mode); *(dfield_ptr++)= *field_ptr; if (!field->default_value->expr) goto end; @@ -1844,7 +1847,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, const uchar *frm_image, size_t frm_length, const uchar *par_image, - size_t par_length) + size_t par_length, bool frm_parser_mode) { TABLE_SHARE *share= this; uint new_frm_ver, field_pack_length, new_field_pack_flag; @@ -3619,6 +3622,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, void TABLE_SHARE::update_optimizer_costs(handlerton *hton) { + if (global_frm_parser_mode) + return; if (hton != view_pseudo_hton && !(hton->flags & HTON_HIDDEN)) { mysql_mutex_lock(&LOCK_optimizer_costs); @@ -4123,7 +4128,7 @@ bool Virtual_column_info::check_access(THD *thd) static Virtual_column_info * unpack_vcol_info_from_frm(THD *thd, TABLE *table, String *expr_str, Virtual_column_info **vcol_ptr, - bool *error_reported) + bool *error_reported, bool frm_parser_mode=false) { Create_field vcol_storage; // placeholder for vcol_info Parser_state parser_state; @@ -4135,7 +4140,21 @@ unpack_vcol_info_from_frm(THD *thd, TABLE *table, DBUG_ENTER("unpack_vcol_info_from_frm"); DBUG_ASSERT(vcol->expr == NULL); - + + if (frm_parser_mode) + { + Frm_parser_item *frm_item= new(thd->mem_root) Frm_parser_item(thd, expr_str->c_ptr_safe(), + expr_str->length()); + vcol_info= new Virtual_column_info(); + vcol_info->expr= frm_item; + vcol_info->set_vcol_type(vcol->get_vcol_type()); + vcol_info->name= vcol->name; + vcol_info->utf8= vcol->utf8; + *vcol_ptr= vcol_info; + DBUG_RETURN(vcol_info); + } + + if (parser_state.init(thd, expr_str->c_ptr_safe(), expr_str->length())) goto end; @@ -4384,7 +4403,7 @@ void TABLE::update_keypart_vcol_info() enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share, const LEX_CSTRING *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, - bool is_create_table, List *partitions_to_open) + bool is_create_table, List *partitions_to_open, bool frm_parser_mode) { enum open_frm_error error; uint records, i, bitmap_size, bitmap_count; @@ -4578,7 +4597,7 @@ enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share, } if (parse_vcol_defs(thd, &outparam->mem_root, outparam, - &error_reported, mode)) + &error_reported, mode, frm_parser_mode)) { error= OPEN_FRM_CORRUPTED; goto err; diff --git a/sql/table.h b/sql/table.h index ba15cf45a1257..3917e8b149d97 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1221,7 +1221,7 @@ struct TABLE_SHARE int init_from_binary_frm_image(THD *thd, bool write, const uchar *frm_image, size_t frm_length, const uchar *par_image=0, - size_t par_length=0); + size_t par_length=0, bool frm_parser_mode=false); /* populates TABLE_SHARE from the table description, specified as the @@ -3616,10 +3616,10 @@ enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share, const LEX_CSTRING *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, bool is_create_table, - List *partitions_to_open= NULL); + List *partitions_to_open= NULL, bool frm_parser_mode=false); bool copy_keys_from_share(TABLE *outparam, MEM_ROOT *root); bool parse_vcol_defs(THD *thd, MEM_ROOT *mem_root, TABLE *table, - bool *error_reported, vcol_init_mode expr); + bool *error_reported, vcol_init_mode expr, bool frm_parser_mode=false); TABLE_SHARE *alloc_table_share(const char *db, const char *table_name, const char *key, uint key_length); void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, @@ -3849,6 +3849,9 @@ class TR_table: public TABLE_LIST } }; +extern bool global_frm_parser_mode; + #endif /* MYSQL_CLIENT */ #endif /* TABLE_INCLUDED */ +