diff options
Diffstat (limited to 'storage/innobase/handler')
-rw-r--r-- | storage/innobase/handler/ha_innodb.cc | 21217 | ||||
-rw-r--r-- | storage/innobase/handler/ha_innodb.h | 937 | ||||
-rw-r--r-- | storage/innobase/handler/handler0alter.cc | 11843 | ||||
-rw-r--r-- | storage/innobase/handler/i_s.cc | 6506 | ||||
-rw-r--r-- | storage/innobase/handler/i_s.h | 91 |
5 files changed, 40594 insertions, 0 deletions
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc new file mode 100644 index 00000000..21bf10a1 --- /dev/null +++ b/storage/innobase/handler/ha_innodb.cc @@ -0,0 +1,21217 @@ +/***************************************************************************** + +Copyright (c) 2000, 2020, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2009, Percona Inc. +Copyright (c) 2012, Facebook Inc. +Copyright (c) 2013, 2023, MariaDB Corporation. + +Portions of this file contain modifications contributed and copyrighted +by Percona Inc.. Those modifications are +gratefully acknowledged and are described briefly in the InnoDB +documentation. The contributions by Percona Inc. are incorporated with +their permission, and subject to the conditions contained in the file +COPYING.Percona. + +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 Street, Fifth Floor, Boston, MA 02110-1335 USA + +*****************************************************************************/ + +/** @file ha_innodb.cc */ + +#include "univ.i" + +/* Include necessary SQL headers */ +#include "ha_prototypes.h" +#include <debug_sync.h> +#include <gstream.h> +#include <log.h> +#include <mysys_err.h> +#include <innodb_priv.h> +#include <strfunc.h> +#include <sql_acl.h> +#include <sql_class.h> +#include <sql_show.h> +#include <sql_table.h> +#include <table_cache.h> +#include <my_check_opt.h> +#include <my_bitmap.h> +#include <mysql/service_thd_alloc.h> +#include <mysql/service_thd_wait.h> +#include "sql_type_geom.h" +#include "scope.h" +#include "srv0srv.h" + +// MYSQL_PLUGIN_IMPORT extern my_bool lower_case_file_system; +// MYSQL_PLUGIN_IMPORT extern char mysql_unpacked_real_data_home[]; + +#include <my_service_manager.h> +#include <key.h> +#include <sql_manager.h> + +/* Include necessary InnoDB headers */ +#include "btr0btr.h" +#include "btr0cur.h" +#include "btr0bulk.h" +#include "btr0sea.h" +#include "buf0dblwr.h" +#include "buf0dump.h" +#include "buf0buf.h" +#include "buf0flu.h" +#include "buf0lru.h" +#include "dict0boot.h" +#include "dict0load.h" +#include "btr0defragment.h" +#include "dict0crea.h" +#include "dict0stats.h" +#include "dict0stats_bg.h" +#include "fil0fil.h" +#include "fsp0fsp.h" +#include "fts0fts.h" +#include "fts0plugin.h" +#include "fts0priv.h" +#include "fts0types.h" +#include "ibuf0ibuf.h" +#include "lock0lock.h" +#include "log0crypt.h" +#include "mtr0mtr.h" +#include "os0file.h" +#include "page0zip.h" +#include "row0import.h" +#include "row0ins.h" +#include "row0log.h" +#include "row0merge.h" +#include "row0mysql.h" +#include "row0quiesce.h" +#include "row0sel.h" +#include "row0upd.h" +#include "fil0crypt.h" +#include "srv0mon.h" +#include "srv0start.h" +#include "rem0rec.h" +#include "trx0purge.h" +#include "trx0roll.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "fil0pagecompress.h" +#include "ut0mem.h" +#include "row0ext.h" +#include "mariadb_stats.h" +thread_local ha_handler_stats mariadb_dummy_stats; +thread_local ha_handler_stats *mariadb_stats= &mariadb_dummy_stats; + +#include "lz4.h" +#include "lzo/lzo1x.h" +#include "lzma.h" +#include "bzlib.h" +#include "snappy-c.h" + +#include <limits> + +#define thd_get_trx_isolation(X) ((enum_tx_isolation)thd_tx_isolation(X)) + +extern "C" void thd_mark_transaction_to_rollback(MYSQL_THD thd, bool all); +unsigned long long thd_get_query_id(const MYSQL_THD thd); +void thd_clear_error(MYSQL_THD thd); + +TABLE *find_fk_open_table(THD *thd, const char *db, size_t db_len, + const char *table, size_t table_len); +MYSQL_THD create_background_thd(); +void reset_thd(MYSQL_THD thd); +TABLE *get_purge_table(THD *thd); +TABLE *open_purge_table(THD *thd, const char *db, size_t dblen, + const char *tb, size_t tblen); +void close_thread_tables(THD* thd); + +#ifdef MYSQL_DYNAMIC_PLUGIN +#define tc_size 400 +#endif + +#include <mysql/plugin.h> +#include <mysql/service_wsrep.h> + +#include "ha_innodb.h" +#include "i_s.h" + +#include <string> +#include <sstream> + +#ifdef WITH_WSREP +#include <mysql/service_md5.h> +#include "wsrep_sst.h" +#endif /* WITH_WSREP */ + +#ifdef HAVE_URING +/** The Linux kernel version if io_uring() is considered unsafe */ +const char *io_uring_may_be_unsafe; +#endif + +#define INSIDE_HA_INNOBASE_CC + +#define EQ_CURRENT_THD(thd) ((thd) == current_thd) + +struct handlerton* innodb_hton_ptr; + +static const long AUTOINC_OLD_STYLE_LOCKING = 0; +static const long AUTOINC_NEW_STYLE_LOCKING = 1; +static const long AUTOINC_NO_LOCKING = 2; + +static constexpr size_t buf_pool_chunk_min_size= 1U << 20; + +static ulong innobase_open_files; +static long innobase_autoinc_lock_mode; + +ulonglong innobase_buffer_pool_size; + +/** Percentage of the buffer pool to reserve for 'old' blocks. +Connected to buf_LRU_old_ratio. */ +static uint innobase_old_blocks_pct; + +static char* innobase_data_file_path; +static char* innobase_temp_data_file_path; + +/* The default values for the following char* start-up parameters +are determined in innodb_init_params(). */ + +static char* innobase_data_home_dir; +static char* innobase_enable_monitor_counter; +static char* innobase_disable_monitor_counter; +static char* innobase_reset_monitor_counter; +static char* innobase_reset_all_monitor_counter; + +/* This variable can be set in the server configure file, specifying +stopword table to be used */ +static char* innobase_server_stopword_table; + +my_bool innobase_rollback_on_timeout; +static my_bool innobase_create_status_file; +my_bool innobase_stats_on_metadata; +static my_bool innodb_optimize_fulltext_only; + +extern uint srv_fil_crypt_rotate_key_age; +extern uint srv_n_fil_crypt_iops; + +#ifdef UNIV_DEBUG +my_bool innodb_evict_tables_on_commit_debug; +#endif + +/** File format constraint for ALTER TABLE */ +ulong innodb_instant_alter_column_allowed; + +/** Note we cannot use rec_format_enum because we do not allow +COMPRESSED row format for innodb_default_row_format option. */ +enum default_row_format_enum { + DEFAULT_ROW_FORMAT_REDUNDANT = 0, + DEFAULT_ROW_FORMAT_COMPACT = 1, + DEFAULT_ROW_FORMAT_DYNAMIC = 2, +}; + +/** Whether ROW_FORMAT=COMPRESSED tables are read-only */ +static my_bool innodb_read_only_compressed; + +/** A dummy variable */ +static uint innodb_max_purge_lag_wait; + +/** Wait for trx_sys.history_size() to be below a limit. */ +static void innodb_max_purge_lag_wait_update(THD *thd, st_mysql_sys_var *, + void *, const void *limit) +{ + if (high_level_read_only) + return; + const uint l= *static_cast<const uint*>(limit); + if (!trx_sys.history_exceeds(l)) + return; + mysql_mutex_unlock(&LOCK_global_system_variables); + while (trx_sys.history_exceeds(l)) + { + if (thd_kill_level(thd)) + break; + /* Adjust for purge_coordinator_state::refresh() */ + log_sys.latch.rd_lock(SRW_LOCK_CALL); + const lsn_t last= log_sys.last_checkpoint_lsn, + max_age= log_sys.max_checkpoint_age; + log_sys.latch.rd_unlock(); + const lsn_t lsn= log_sys.get_lsn(); + if ((lsn - last) / 4 >= max_age / 5) + buf_flush_ahead(last + max_age / 5, false); + purge_sys.wake_if_not_active(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + mysql_mutex_lock(&LOCK_global_system_variables); +} + +static +void set_my_errno(int err) +{ + errno = err; +} + +/** Checks whether the file name belongs to a partition of a table. +@param[in] file_name file name +@return pointer to the end of the table name part of the file name, or NULL */ +static +char* +is_partition( +/*=========*/ + char* file_name) +{ + /* We look for pattern #P# to see if the table is partitioned + MariaDB table. */ + return strstr(file_name, table_name_t::part_suffix); +} + + + +/** Return the InnoDB ROW_FORMAT enum value +@param[in] row_format row_format from "innodb_default_row_format" +@return InnoDB ROW_FORMAT value from rec_format_t enum. */ +static +rec_format_t +get_row_format( + ulong row_format) +{ + switch(row_format) { + case DEFAULT_ROW_FORMAT_REDUNDANT: + return(REC_FORMAT_REDUNDANT); + case DEFAULT_ROW_FORMAT_COMPACT: + return(REC_FORMAT_COMPACT); + case DEFAULT_ROW_FORMAT_DYNAMIC: + return(REC_FORMAT_DYNAMIC); + default: + ut_ad(0); + return(REC_FORMAT_DYNAMIC); + } +} + +static ulong innodb_default_row_format = DEFAULT_ROW_FORMAT_DYNAMIC; + +/** Possible values for system variable "innodb_stats_method". The values +are defined the same as its corresponding MyISAM system variable +"myisam_stats_method"(see "myisam_stats_method_names"), for better usability */ +static const char* innodb_stats_method_names[] = { + "nulls_equal", + "nulls_unequal", + "nulls_ignored", + NullS +}; + +/** Used to define an enumerate type of the system variable innodb_stats_method. +This is the same as "myisam_stats_method_typelib" */ +static TYPELIB innodb_stats_method_typelib = { + array_elements(innodb_stats_method_names) - 1, + "innodb_stats_method_typelib", + innodb_stats_method_names, + NULL +}; + +/** Possible values of the parameter innodb_checksum_algorithm */ +const char* innodb_checksum_algorithm_names[] = { + "crc32", + "strict_crc32", + "full_crc32", + "strict_full_crc32", + NullS +}; + +/** Used to define an enumerate type of the system variable +innodb_checksum_algorithm. */ +TYPELIB innodb_checksum_algorithm_typelib = { + array_elements(innodb_checksum_algorithm_names) - 1, + "innodb_checksum_algorithm_typelib", + innodb_checksum_algorithm_names, + NULL +}; + +/** Possible values for system variable "innodb_default_row_format". */ +static const char* innodb_default_row_format_names[] = { + "redundant", + "compact", + "dynamic", + NullS +}; + +/** Used to define an enumerate type of the system variable +innodb_default_row_format. */ +static TYPELIB innodb_default_row_format_typelib = { + array_elements(innodb_default_row_format_names) - 1, + "innodb_default_row_format_typelib", + innodb_default_row_format_names, + NULL +}; + +/** Names of allowed values of innodb_flush_method */ +const char* innodb_flush_method_names[] = { + "fsync", + "O_DSYNC", + "littlesync", + "nosync", + "O_DIRECT", + "O_DIRECT_NO_FSYNC", +#ifdef _WIN32 + "unbuffered", + "async_unbuffered" /* alias for "unbuffered" */, + "normal" /* alias for "fsync" */, +#endif + NullS +}; + +/** Enumeration of innodb_flush_method */ +TYPELIB innodb_flush_method_typelib = { + array_elements(innodb_flush_method_names) - 1, + "innodb_flush_method_typelib", + innodb_flush_method_names, + NULL +}; + +/** Names of allowed values of innodb_deadlock_report */ +static const char *innodb_deadlock_report_names[]= { + "off", /* Do not report any details of deadlocks */ + "basic", /* Report waiting transactions and lock requests */ + "full", /* Also report blocking locks */ + NullS +}; + +static_assert(Deadlock::REPORT_OFF == 0, "compatibility"); +static_assert(Deadlock::REPORT_BASIC == 1, "compatibility"); +static_assert(Deadlock::REPORT_FULL == 2, "compatibility"); + +/** Enumeration of innodb_deadlock_report */ +static TYPELIB innodb_deadlock_report_typelib = { + array_elements(innodb_deadlock_report_names) - 1, + "innodb_deadlock_report_typelib", + innodb_deadlock_report_names, + NULL +}; + +/** Allowed values of innodb_change_buffering */ +static const char* innodb_change_buffering_names[] = { + "none", /* IBUF_USE_NONE */ + "inserts", /* IBUF_USE_INSERT */ + "deletes", /* IBUF_USE_DELETE_MARK */ + "changes", /* IBUF_USE_INSERT_DELETE_MARK */ + "purges", /* IBUF_USE_DELETE */ + "all", /* IBUF_USE_ALL */ + NullS +}; + +/** Enumeration of innodb_change_buffering */ +static TYPELIB innodb_change_buffering_typelib = { + array_elements(innodb_change_buffering_names) - 1, + "innodb_change_buffering_typelib", + innodb_change_buffering_names, + NULL +}; + +/** Allowed values of innodb_instant_alter_column_allowed */ +const char* innodb_instant_alter_column_allowed_names[] = { + "never", /* compatible with MariaDB 5.5 to 10.2 */ + "add_last",/* allow instant ADD COLUMN ... LAST */ + "add_drop_reorder", /* allow instant ADD anywhere & DROP & reorder */ + NullS +}; + +/** Enumeration of innodb_instant_alter_column_allowed */ +static TYPELIB innodb_instant_alter_column_allowed_typelib = { + array_elements(innodb_instant_alter_column_allowed_names) - 1, + "innodb_instant_alter_column_allowed_typelib", + innodb_instant_alter_column_allowed_names, + NULL +}; + +/** Retrieve the FTS Relevance Ranking result for doc with doc_id +of m_prebuilt->fts_doc_id +@param[in,out] fts_hdl FTS handler +@return the relevance ranking value */ +static +float +innobase_fts_retrieve_ranking( + FT_INFO* fts_hdl); +/** Free the memory for the FTS handler +@param[in,out] fts_hdl FTS handler */ +static +void +innobase_fts_close_ranking( + FT_INFO* fts_hdl); +/** Find and Retrieve the FTS Relevance Ranking result for doc with doc_id +of m_prebuilt->fts_doc_id +@param[in,out] fts_hdl FTS handler +@return the relevance ranking value */ +static +float +innobase_fts_find_ranking( + FT_INFO* fts_hdl, + uchar*, + uint); + +/* Call back function array defined by MySQL and used to +retrieve FTS results. */ +const struct _ft_vft ft_vft_result = {NULL, + innobase_fts_find_ranking, + innobase_fts_close_ranking, + innobase_fts_retrieve_ranking, + NULL}; + +/** @return version of the extended FTS API */ +static +uint +innobase_fts_get_version() +{ + /* Currently this doesn't make much sense as returning + HA_CAN_FULLTEXT_EXT automatically mean this version is supported. + This supposed to ease future extensions. */ + return(2); +} + +/** @return Which part of the extended FTS API is supported */ +static +ulonglong +innobase_fts_flags() +{ + return(FTS_ORDERED_RESULT | FTS_DOCID_IN_RESULT); +} + +/** Find and Retrieve the FTS doc_id for the current result row +@param[in,out] fts_hdl FTS handler +@return the document ID */ +static +ulonglong +innobase_fts_retrieve_docid( + FT_INFO_EXT* fts_hdl); + +/** Find and retrieve the size of the current result +@param[in,out] fts_hdl FTS handler +@return number of matching rows */ +static +ulonglong +innobase_fts_count_matches( + FT_INFO_EXT* fts_hdl) /*!< in: FTS handler */ +{ + NEW_FT_INFO* handle = reinterpret_cast<NEW_FT_INFO*>(fts_hdl); + + if (handle->ft_result->rankings_by_id != NULL) { + return(rbt_size(handle->ft_result->rankings_by_id)); + } else { + return(0); + } +} + +const struct _ft_vft_ext ft_vft_ext_result = {innobase_fts_get_version, + innobase_fts_flags, + innobase_fts_retrieve_docid, + innobase_fts_count_matches}; + +#ifdef HAVE_PSI_INTERFACE +# define PSI_KEY(n) {&n##_key, #n, 0} +/* Keys to register pthread mutexes in the current file with +performance schema */ +static mysql_pfs_key_t pending_checkpoint_mutex_key; + +# ifdef UNIV_PFS_MUTEX +mysql_pfs_key_t buf_pool_mutex_key; +mysql_pfs_key_t dict_foreign_err_mutex_key; +mysql_pfs_key_t fil_system_mutex_key; +mysql_pfs_key_t flush_list_mutex_key; +mysql_pfs_key_t fts_cache_mutex_key; +mysql_pfs_key_t fts_cache_init_mutex_key; +mysql_pfs_key_t fts_delete_mutex_key; +mysql_pfs_key_t fts_doc_id_mutex_key; +mysql_pfs_key_t ibuf_bitmap_mutex_key; +mysql_pfs_key_t ibuf_mutex_key; +mysql_pfs_key_t ibuf_pessimistic_insert_mutex_key; +mysql_pfs_key_t recalc_pool_mutex_key; +mysql_pfs_key_t purge_sys_pq_mutex_key; +mysql_pfs_key_t recv_sys_mutex_key; +mysql_pfs_key_t page_zip_stat_per_index_mutex_key; +mysql_pfs_key_t rtr_active_mutex_key; +mysql_pfs_key_t rtr_match_mutex_key; +mysql_pfs_key_t rtr_path_mutex_key; +mysql_pfs_key_t srv_innodb_monitor_mutex_key; +mysql_pfs_key_t srv_misc_tmpfile_mutex_key; +mysql_pfs_key_t srv_monitor_file_mutex_key; +mysql_pfs_key_t buf_dblwr_mutex_key; +mysql_pfs_key_t trx_pool_mutex_key; +mysql_pfs_key_t trx_pool_manager_mutex_key; +mysql_pfs_key_t lock_wait_mutex_key; +mysql_pfs_key_t trx_sys_mutex_key; +mysql_pfs_key_t srv_threads_mutex_key; +mysql_pfs_key_t tpool_cache_mutex_key; + +/* all_innodb_mutexes array contains mutexes that are +performance schema instrumented if "UNIV_PFS_MUTEX" +is defined */ +static PSI_mutex_info all_innodb_mutexes[] = { + PSI_KEY(pending_checkpoint_mutex), + PSI_KEY(buf_pool_mutex), + PSI_KEY(dict_foreign_err_mutex), + PSI_KEY(recalc_pool_mutex), + PSI_KEY(fil_system_mutex), + PSI_KEY(flush_list_mutex), + PSI_KEY(fts_cache_mutex), + PSI_KEY(fts_cache_init_mutex), + PSI_KEY(fts_delete_mutex), + PSI_KEY(fts_doc_id_mutex), + PSI_KEY(ibuf_mutex), + PSI_KEY(ibuf_pessimistic_insert_mutex), + PSI_KEY(index_online_log), + PSI_KEY(page_zip_stat_per_index_mutex), + PSI_KEY(purge_sys_pq_mutex), + PSI_KEY(recv_sys_mutex), + PSI_KEY(srv_innodb_monitor_mutex), + PSI_KEY(srv_misc_tmpfile_mutex), + PSI_KEY(srv_monitor_file_mutex), + PSI_KEY(buf_dblwr_mutex), + PSI_KEY(trx_pool_mutex), + PSI_KEY(trx_pool_manager_mutex), + PSI_KEY(lock_wait_mutex), + PSI_KEY(srv_threads_mutex), + PSI_KEY(rtr_active_mutex), + PSI_KEY(rtr_match_mutex), + PSI_KEY(rtr_path_mutex), + PSI_KEY(trx_sys_mutex), + PSI_KEY(tpool_cache_mutex), +}; +# endif /* UNIV_PFS_MUTEX */ + +# ifdef UNIV_PFS_RWLOCK +mysql_pfs_key_t dict_operation_lock_key; +mysql_pfs_key_t index_tree_rw_lock_key; +mysql_pfs_key_t index_online_log_key; +mysql_pfs_key_t fil_space_latch_key; +mysql_pfs_key_t trx_i_s_cache_lock_key; +mysql_pfs_key_t trx_purge_latch_key; +mysql_pfs_key_t trx_rseg_latch_key; +mysql_pfs_key_t lock_latch_key; +mysql_pfs_key_t log_latch_key; + +/* all_innodb_rwlocks array contains rwlocks that are +performance schema instrumented if "UNIV_PFS_RWLOCK" +is defined */ +static PSI_rwlock_info all_innodb_rwlocks[] = +{ +# ifdef BTR_CUR_HASH_ADAPT + { &btr_search_latch_key, "btr_search_latch", 0 }, +# endif + { &dict_operation_lock_key, "dict_operation_lock", 0 }, + { &fil_space_latch_key, "fil_space_latch", 0 }, + { &trx_i_s_cache_lock_key, "trx_i_s_cache_lock", 0 }, + { &trx_purge_latch_key, "trx_purge_latch", 0 }, + { &trx_rseg_latch_key, "trx_rseg_latch", 0 }, + { &lock_latch_key, "lock_latch", 0 }, + { &log_latch_key, "log_latch", 0 }, + { &index_tree_rw_lock_key, "index_tree_rw_lock", PSI_RWLOCK_FLAG_SX } +}; +# endif /* UNIV_PFS_RWLOCK */ + +# ifdef UNIV_PFS_THREAD +/* all_innodb_threads array contains threads that are +performance schema instrumented if "UNIV_PFS_THREAD" +is defined */ +static PSI_thread_info all_innodb_threads[] = { + PSI_KEY(page_cleaner_thread), + PSI_KEY(trx_rollback_clean_thread), + PSI_KEY(thread_pool_thread) +}; +# endif /* UNIV_PFS_THREAD */ + +# ifdef UNIV_PFS_IO +/* all_innodb_files array contains the type of files that are +performance schema instrumented if "UNIV_PFS_IO" is defined */ +static PSI_file_info all_innodb_files[] = { + PSI_KEY(innodb_data_file), + PSI_KEY(innodb_temp_file) +}; +# endif /* UNIV_PFS_IO */ +#endif /* HAVE_PSI_INTERFACE */ + +static void innodb_remember_check_sysvar_funcs(); +mysql_var_check_func check_sysvar_enum; +mysql_var_check_func check_sysvar_int; + +// should page compression be used by default for new tables +static MYSQL_THDVAR_BOOL(compression_default, PLUGIN_VAR_OPCMDARG, + "Is compression the default for new tables", + NULL, NULL, FALSE); + +/** Update callback for SET [SESSION] innodb_default_encryption_key_id */ +static void +innodb_default_encryption_key_id_update(THD* thd, st_mysql_sys_var* var, + void* var_ptr, const void *save) +{ + uint key_id = *static_cast<const uint*>(save); + if (key_id != FIL_DEFAULT_ENCRYPTION_KEY + && !encryption_key_id_exists(key_id)) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "innodb_default_encryption_key=%u" + " is not available", key_id); + } + *static_cast<uint*>(var_ptr) = key_id; +} + +static MYSQL_THDVAR_UINT(default_encryption_key_id, PLUGIN_VAR_RQCMDARG, + "Default encryption key id used for table encryption.", + NULL, innodb_default_encryption_key_id_update, + FIL_DEFAULT_ENCRYPTION_KEY, 1, UINT_MAX32, 0); + +/** + Structure for CREATE TABLE options (table options). + It needs to be called ha_table_option_struct. + + The option values can be specified in the CREATE TABLE at the end: + CREATE TABLE ( ... ) *here* +*/ + +ha_create_table_option innodb_table_option_list[]= +{ + /* With this option user can enable page compression feature for the + table */ + HA_TOPTION_SYSVAR("PAGE_COMPRESSED", page_compressed, compression_default), + /* With this option user can set zip compression level for page + compression for this table*/ + HA_TOPTION_NUMBER("PAGE_COMPRESSION_LEVEL", page_compression_level, 0, 1, 9, 1), + /* With this option the user can enable encryption for the table */ + HA_TOPTION_ENUM("ENCRYPTED", encryption, "DEFAULT,YES,NO", 0), + /* With this option the user defines the key identifier using for the encryption */ + HA_TOPTION_SYSVAR("ENCRYPTION_KEY_ID", encryption_key_id, default_encryption_key_id), + + HA_TOPTION_END +}; + +/*************************************************************//** +Check whether valid argument given to innodb_ft_*_stopword_table. +This function is registered as a callback with MySQL. +@return 0 for valid stopword table */ +static +int +innodb_stopword_table_validate( +/*===========================*/ + THD* thd, /*!< in: thread handle */ + struct st_mysql_sys_var* var, /*!< in: pointer to system + variable */ + void* save, /*!< out: immediate result + for update function */ + struct st_mysql_value* value); /*!< in: incoming string */ + +static +void innodb_ft_cache_size_update(THD*, st_mysql_sys_var*, void*, const void* save) +{ + fts_max_cache_size= *static_cast<const size_t*>(save); +} + +static +void innodb_ft_total_cache_size_update(THD*, st_mysql_sys_var*, void*, const void* save) +{ + fts_max_total_cache_size= *static_cast<const size_t*>(save); +} + +static bool is_mysql_datadir_path(const char *path); + +/** Validate passed-in "value" is a valid directory name. +This function is registered as a callback with MySQL. +@param[in,out] thd thread handle +@param[in] var pointer to system variable +@param[out] save immediate result for update +@param[in] value incoming string +@return 0 for valid name */ +static +int +innodb_tmpdir_validate( + THD* thd, + struct st_mysql_sys_var*, + void* save, + struct st_mysql_value* value) +{ + + char* alter_tmp_dir; + char* innodb_tmp_dir; + char buff[OS_FILE_MAX_PATH]; + int len = sizeof(buff); + char tmp_abs_path[FN_REFLEN + 2]; + + ut_ad(save != NULL); + ut_ad(value != NULL); + + if (check_global_access(thd, FILE_ACL)) { + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "InnoDB: FILE Permissions required"); + *static_cast<const char**>(save) = NULL; + return(1); + } + + alter_tmp_dir = (char*) value->val_str(value, buff, &len); + + if (!alter_tmp_dir) { + *static_cast<const char**>(save) = alter_tmp_dir; + return(0); + } + + if (strlen(alter_tmp_dir) > FN_REFLEN) { + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Path length should not exceed %d bytes", FN_REFLEN); + *static_cast<const char**>(save) = NULL; + return(1); + } + + my_realpath(tmp_abs_path, alter_tmp_dir, 0); + size_t tmp_abs_len = strlen(tmp_abs_path); + + if (my_access(tmp_abs_path, F_OK)) { + + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "InnoDB: Path doesn't exist."); + *static_cast<const char**>(save) = NULL; + return(1); + } else if (my_access(tmp_abs_path, R_OK | W_OK)) { + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "InnoDB: Server doesn't have permission in " + "the given location."); + *static_cast<const char**>(save) = NULL; + return(1); + } + + MY_STAT stat_info_dir; + + if (my_stat(tmp_abs_path, &stat_info_dir, MYF(0))) { + if ((stat_info_dir.st_mode & S_IFDIR) != S_IFDIR) { + + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Given path is not a directory. "); + *static_cast<const char**>(save) = NULL; + return(1); + } + } + + if (!is_mysql_datadir_path(tmp_abs_path)) { + + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "InnoDB: Path Location should not be same as " + "mysql data directory location."); + *static_cast<const char**>(save) = NULL; + return(1); + } + + innodb_tmp_dir = static_cast<char*>( + thd_memdup(thd, tmp_abs_path, tmp_abs_len + 1)); + *static_cast<const char**>(save) = innodb_tmp_dir; + return(0); +} + +/******************************************************************//** +Maps a MySQL trx isolation level code to the InnoDB isolation level code +@return InnoDB isolation level */ +static inline +uint +innobase_map_isolation_level( +/*=========================*/ + enum_tx_isolation iso); /*!< in: MySQL isolation level code */ + +/** Gets field offset for a field in a table. +@param[in] table MySQL table object +@param[in] field MySQL field object (from table->field array) +@return offset */ +static inline +uint +get_field_offset( + const TABLE* table, + const Field* field) +{ + return field->offset(table->record[0]); +} + + +/*************************************************************//** +Check for a valid value of innobase_compression_algorithm. +@return 0 for valid innodb_compression_algorithm. */ +static +int +innodb_compression_algorithm_validate( +/*==================================*/ + THD* thd, /*!< in: thread handle */ + struct st_mysql_sys_var* var, /*!< in: pointer to system + variable */ + void* save, /*!< out: immediate result + for update function */ + struct st_mysql_value* value); /*!< in: incoming string */ + +static ibool innodb_have_punch_hole=IF_PUNCH_HOLE(1, 0); + +static +int +innodb_encrypt_tables_validate( +/*==================================*/ + THD* thd, /*!< in: thread handle */ + struct st_mysql_sys_var* var, /*!< in: pointer to system + variable */ + void* save, /*!< out: immediate result + for update function */ + struct st_mysql_value* value); /*!< in: incoming string */ + +static const char innobase_hton_name[]= "InnoDB"; + +static MYSQL_THDVAR_BOOL(table_locks, PLUGIN_VAR_OPCMDARG, + "Enable InnoDB locking in LOCK TABLES", + /* check_func */ NULL, /* update_func */ NULL, + /* default */ TRUE); + +static MYSQL_THDVAR_BOOL(strict_mode, PLUGIN_VAR_OPCMDARG, + "Use strict mode when evaluating create options.", + NULL, NULL, TRUE); + +static MYSQL_THDVAR_BOOL(ft_enable_stopword, PLUGIN_VAR_OPCMDARG, + "Create FTS index with stopword.", + NULL, NULL, + /* default */ TRUE); + +static MYSQL_THDVAR_UINT(lock_wait_timeout, PLUGIN_VAR_RQCMDARG, + "Timeout in seconds an InnoDB transaction may wait for a lock before being rolled back. The value 100000000 is infinite timeout.", + NULL, NULL, 50, 0, 100000000, 0); + +static MYSQL_THDVAR_STR(ft_user_stopword_table, + PLUGIN_VAR_OPCMDARG|PLUGIN_VAR_MEMALLOC, + "User supplied stopword table name, effective in the session level.", + innodb_stopword_table_validate, NULL, NULL); + +static MYSQL_THDVAR_STR(tmpdir, + PLUGIN_VAR_OPCMDARG|PLUGIN_VAR_MEMALLOC, + "Directory for temporary non-tablespace files.", + innodb_tmpdir_validate, NULL, NULL); + +static size_t truncated_status_writes; + +static SHOW_VAR innodb_status_variables[]= { +#ifdef BTR_CUR_HASH_ADAPT + {"adaptive_hash_hash_searches", &export_vars.innodb_ahi_hit, SHOW_SIZE_T}, + {"adaptive_hash_non_hash_searches", + &export_vars.innodb_ahi_miss, SHOW_SIZE_T}, +#endif + {"background_log_sync", &srv_log_writes_and_flush, SHOW_SIZE_T}, + {"buffer_pool_dump_status", + (char*) &export_vars.innodb_buffer_pool_dump_status, SHOW_CHAR}, + {"buffer_pool_load_status", + (char*) &export_vars.innodb_buffer_pool_load_status, SHOW_CHAR}, + {"buffer_pool_resize_status", + (char*) &export_vars.innodb_buffer_pool_resize_status, SHOW_CHAR}, + {"buffer_pool_load_incomplete", + &export_vars.innodb_buffer_pool_load_incomplete, SHOW_BOOL}, + {"buffer_pool_pages_data", &UT_LIST_GET_LEN(buf_pool.LRU), SHOW_SIZE_T}, + {"buffer_pool_bytes_data", + &export_vars.innodb_buffer_pool_bytes_data, SHOW_SIZE_T}, + {"buffer_pool_pages_dirty", + &UT_LIST_GET_LEN(buf_pool.flush_list), SHOW_SIZE_T}, + {"buffer_pool_bytes_dirty", &buf_pool.flush_list_bytes, SHOW_SIZE_T}, + {"buffer_pool_pages_flushed", &buf_pool.stat.n_pages_written, SHOW_SIZE_T}, + {"buffer_pool_pages_free", &UT_LIST_GET_LEN(buf_pool.free), SHOW_SIZE_T}, +#ifdef UNIV_DEBUG + {"buffer_pool_pages_latched", + &export_vars.innodb_buffer_pool_pages_latched, SHOW_SIZE_T}, +#endif /* UNIV_DEBUG */ + {"buffer_pool_pages_made_not_young", + &buf_pool.stat.n_pages_not_made_young, SHOW_SIZE_T}, + {"buffer_pool_pages_made_young", + &buf_pool.stat.n_pages_made_young, SHOW_SIZE_T}, + {"buffer_pool_pages_misc", + &export_vars.innodb_buffer_pool_pages_misc, SHOW_SIZE_T}, + {"buffer_pool_pages_old", &buf_pool.LRU_old_len, SHOW_SIZE_T}, + {"buffer_pool_pages_total", + &export_vars.innodb_buffer_pool_pages_total, SHOW_SIZE_T}, + {"buffer_pool_pages_LRU_flushed", &buf_lru_flush_page_count, SHOW_SIZE_T}, + {"buffer_pool_pages_LRU_freed", &buf_lru_freed_page_count, SHOW_SIZE_T}, + {"buffer_pool_pages_split", &buf_pool.pages_split, SHOW_SIZE_T}, + {"buffer_pool_read_ahead_rnd", + &buf_pool.stat.n_ra_pages_read_rnd, SHOW_SIZE_T}, + {"buffer_pool_read_ahead", &buf_pool.stat.n_ra_pages_read, SHOW_SIZE_T}, + {"buffer_pool_read_ahead_evicted", + &buf_pool.stat.n_ra_pages_evicted, SHOW_SIZE_T}, + {"buffer_pool_read_requests", + &export_vars.innodb_buffer_pool_read_requests, SHOW_SIZE_T}, + {"buffer_pool_reads", &buf_pool.stat.n_pages_read, SHOW_SIZE_T}, + {"buffer_pool_wait_free", &buf_pool.stat.LRU_waits, SHOW_SIZE_T}, + {"buffer_pool_write_requests", &buf_pool.flush_list_requests, SHOW_SIZE_T}, + {"checkpoint_age", &export_vars.innodb_checkpoint_age, SHOW_SIZE_T}, + {"checkpoint_max_age", &export_vars.innodb_checkpoint_max_age, SHOW_SIZE_T}, + {"data_fsyncs", (size_t*) &os_n_fsyncs, SHOW_SIZE_T}, + {"data_pending_fsyncs", + (size_t*) &fil_n_pending_tablespace_flushes, SHOW_SIZE_T}, + {"data_pending_reads", &export_vars.innodb_data_pending_reads, SHOW_SIZE_T}, + {"data_pending_writes", &export_vars.innodb_data_pending_writes,SHOW_SIZE_T}, + {"data_read", &export_vars.innodb_data_read, SHOW_SIZE_T}, + {"data_reads", &export_vars.innodb_data_reads, SHOW_SIZE_T}, + {"data_writes", &export_vars.innodb_data_writes, SHOW_SIZE_T}, + {"data_written", &export_vars.innodb_data_written, SHOW_SIZE_T}, + {"dblwr_pages_written", &export_vars.innodb_dblwr_pages_written,SHOW_SIZE_T}, + {"dblwr_writes", &export_vars.innodb_dblwr_writes, SHOW_SIZE_T}, + {"deadlocks", &lock_sys.deadlocks, SHOW_SIZE_T}, + {"history_list_length", &export_vars.innodb_history_list_length,SHOW_SIZE_T}, + {"ibuf_discarded_delete_marks", &ibuf.n_discarded_ops[IBUF_OP_DELETE_MARK], + SHOW_SIZE_T}, + {"ibuf_discarded_deletes", &ibuf.n_discarded_ops[IBUF_OP_DELETE], + SHOW_SIZE_T}, + {"ibuf_discarded_inserts", &ibuf.n_discarded_ops[IBUF_OP_INSERT], + SHOW_SIZE_T}, + {"ibuf_free_list", &ibuf.free_list_len, SHOW_SIZE_T}, + {"ibuf_merged_delete_marks", &ibuf.n_merged_ops[IBUF_OP_DELETE_MARK], + SHOW_SIZE_T}, + {"ibuf_merged_deletes", &ibuf.n_merged_ops[IBUF_OP_DELETE], SHOW_SIZE_T}, + {"ibuf_merged_inserts", &ibuf.n_merged_ops[IBUF_OP_INSERT], SHOW_SIZE_T}, + {"ibuf_merges", &ibuf.n_merges, SHOW_SIZE_T}, + {"ibuf_segment_size", &ibuf.seg_size, SHOW_SIZE_T}, + {"ibuf_size", &ibuf.size, SHOW_SIZE_T}, + {"log_waits", &log_sys.waits, SHOW_SIZE_T}, + {"log_write_requests", &log_sys.write_to_buf, SHOW_SIZE_T}, + {"log_writes", &log_sys.write_to_log, SHOW_SIZE_T}, + {"lsn_current", &export_vars.innodb_lsn_current, SHOW_ULONGLONG}, + {"lsn_flushed", &export_vars.innodb_lsn_flushed, SHOW_ULONGLONG}, + {"lsn_last_checkpoint", &export_vars.innodb_lsn_last_checkpoint, + SHOW_ULONGLONG}, + {"master_thread_active_loops", &srv_main_active_loops, SHOW_SIZE_T}, + {"master_thread_idle_loops", &srv_main_idle_loops, SHOW_SIZE_T}, + {"max_trx_id", &export_vars.innodb_max_trx_id, SHOW_ULONGLONG}, +#ifdef BTR_CUR_HASH_ADAPT + {"mem_adaptive_hash", &export_vars.innodb_mem_adaptive_hash, SHOW_SIZE_T}, +#endif + {"mem_dictionary", &export_vars.innodb_mem_dictionary, SHOW_SIZE_T}, + {"os_log_written", &export_vars.innodb_os_log_written, SHOW_SIZE_T}, + {"page_size", &srv_page_size, SHOW_ULONG}, + {"pages_created", &buf_pool.stat.n_pages_created, SHOW_SIZE_T}, + {"pages_read", &buf_pool.stat.n_pages_read, SHOW_SIZE_T}, + {"pages_written", &buf_pool.stat.n_pages_written, SHOW_SIZE_T}, + {"row_lock_current_waits", &export_vars.innodb_row_lock_current_waits, + SHOW_SIZE_T}, + {"row_lock_time", &export_vars.innodb_row_lock_time, SHOW_LONGLONG}, + {"row_lock_time_avg", &export_vars.innodb_row_lock_time_avg, SHOW_ULONGLONG}, + {"row_lock_time_max", &export_vars.innodb_row_lock_time_max, SHOW_ULONGLONG}, + {"row_lock_waits", &export_vars.innodb_row_lock_waits, SHOW_SIZE_T}, + {"num_open_files", &fil_system.n_open, SHOW_SIZE_T}, + {"truncated_status_writes", &truncated_status_writes, SHOW_SIZE_T}, + {"available_undo_logs", &srv_available_undo_logs, SHOW_ULONG}, + {"undo_truncations", &export_vars.innodb_undo_truncations, SHOW_ULONG}, + + /* Status variables for page compression */ + {"page_compression_saved", + &export_vars.innodb_page_compression_saved, SHOW_LONGLONG}, + {"num_pages_page_compressed", + &export_vars.innodb_pages_page_compressed, SHOW_LONGLONG}, + {"num_page_compressed_trim_op", + &export_vars.innodb_page_compressed_trim_op, SHOW_LONGLONG}, + {"num_pages_page_decompressed", + &export_vars.innodb_pages_page_decompressed, SHOW_LONGLONG}, + {"num_pages_page_compression_error", + &export_vars.innodb_pages_page_compression_error, SHOW_LONGLONG}, + {"num_pages_encrypted", + &export_vars.innodb_pages_encrypted, SHOW_LONGLONG}, + {"num_pages_decrypted", + &export_vars.innodb_pages_decrypted, SHOW_LONGLONG}, + {"have_lz4", &(provider_service_lz4->is_loaded), SHOW_BOOL}, + {"have_lzo", &(provider_service_lzo->is_loaded), SHOW_BOOL}, + {"have_lzma", &(provider_service_lzma->is_loaded), SHOW_BOOL}, + {"have_bzip2", &(provider_service_bzip2->is_loaded), SHOW_BOOL}, + {"have_snappy", &(provider_service_snappy->is_loaded), SHOW_BOOL}, + {"have_punch_hole", &innodb_have_punch_hole, SHOW_BOOL}, + + /* Defragmentation */ + {"defragment_compression_failures", + &export_vars.innodb_defragment_compression_failures, SHOW_SIZE_T}, + {"defragment_failures", &export_vars.innodb_defragment_failures,SHOW_SIZE_T}, + {"defragment_count", &export_vars.innodb_defragment_count, SHOW_SIZE_T}, + + {"instant_alter_column", + &export_vars.innodb_instant_alter_column, SHOW_ULONG}, + + /* Online alter table status variables */ + {"onlineddl_rowlog_rows", + &export_vars.innodb_onlineddl_rowlog_rows, SHOW_SIZE_T}, + {"onlineddl_rowlog_pct_used", + &export_vars.innodb_onlineddl_rowlog_pct_used, SHOW_SIZE_T}, + {"onlineddl_pct_progress", + &export_vars.innodb_onlineddl_pct_progress, SHOW_SIZE_T}, + + /* Encryption */ + {"encryption_rotation_pages_read_from_cache", + &export_vars.innodb_encryption_rotation_pages_read_from_cache, SHOW_SIZE_T}, + {"encryption_rotation_pages_read_from_disk", + &export_vars.innodb_encryption_rotation_pages_read_from_disk, SHOW_SIZE_T}, + {"encryption_rotation_pages_modified", + &export_vars.innodb_encryption_rotation_pages_modified, SHOW_SIZE_T}, + {"encryption_rotation_pages_flushed", + &export_vars.innodb_encryption_rotation_pages_flushed, SHOW_SIZE_T}, + {"encryption_rotation_estimated_iops", + &export_vars.innodb_encryption_rotation_estimated_iops, SHOW_SIZE_T}, + {"encryption_n_merge_blocks_encrypted", + &export_vars.innodb_n_merge_blocks_encrypted, SHOW_LONGLONG}, + {"encryption_n_merge_blocks_decrypted", + &export_vars.innodb_n_merge_blocks_decrypted, SHOW_LONGLONG}, + {"encryption_n_rowlog_blocks_encrypted", + &export_vars.innodb_n_rowlog_blocks_encrypted, SHOW_LONGLONG}, + {"encryption_n_rowlog_blocks_decrypted", + &export_vars.innodb_n_rowlog_blocks_decrypted, SHOW_LONGLONG}, + {"encryption_n_temp_blocks_encrypted", + &export_vars.innodb_n_temp_blocks_encrypted, SHOW_LONGLONG}, + {"encryption_n_temp_blocks_decrypted", + &export_vars.innodb_n_temp_blocks_decrypted, SHOW_LONGLONG}, + {"encryption_num_key_requests", &export_vars.innodb_encryption_key_requests, + SHOW_LONGLONG}, + + {NullS, NullS, SHOW_LONG} +}; + +/*****************************************************************//** +Frees a possible InnoDB trx object associated with the current THD. +@return 0 or error number */ +static +int +innobase_close_connection( +/*======================*/ + handlerton* hton, /*!< in/out: InnoDB handlerton */ + THD* thd); /*!< in: MySQL thread handle for + which to close the connection */ + +/** Cancel any pending lock request associated with the current THD. +@sa THD::awake() @sa ha_kill_query() */ +static void innobase_kill_query(handlerton*, THD* thd, enum thd_kill_levels); +static void innobase_commit_ordered(handlerton *hton, THD* thd, bool all); + +/*****************************************************************//** +Commits a transaction in an InnoDB database or marks an SQL statement +ended. +@return 0 */ +static +int +innobase_commit( +/*============*/ + handlerton* hton, /*!< in/out: InnoDB handlerton */ + THD* thd, /*!< in: MySQL thread handle of the + user for whom the transaction should + be committed */ + bool commit_trx); /*!< in: true - commit transaction + false - the current SQL statement + ended */ + +/*****************************************************************//** +Rolls back a transaction to a savepoint. +@return 0 if success, HA_ERR_NO_SAVEPOINT if no savepoint with the +given name */ +static +int +innobase_rollback( +/*==============*/ + handlerton* hton, /*!< in/out: InnoDB handlerton */ + THD* thd, /*!< in: handle to the MySQL thread + of the user whose transaction should + be rolled back */ + bool rollback_trx); /*!< in: TRUE - rollback entire + transaction FALSE - rollback the current + statement only */ + +/*****************************************************************//** +Rolls back a transaction to a savepoint. +@return 0 if success, HA_ERR_NO_SAVEPOINT if no savepoint with the +given name */ +static +int +innobase_rollback_to_savepoint( +/*===========================*/ + handlerton* hton, /*!< in/out: InnoDB handlerton */ + THD* thd, /*!< in: handle to the MySQL thread of + the user whose XA transaction should + be rolled back to savepoint */ + void* savepoint); /*!< in: savepoint data */ + +/*****************************************************************//** +Check whether innodb state allows to safely release MDL locks after +rollback to savepoint. +@return true if it is safe, false if its not safe. */ +static +bool +innobase_rollback_to_savepoint_can_release_mdl( +/*===========================================*/ + handlerton* hton, /*!< in/out: InnoDB handlerton */ + THD* thd); /*!< in: handle to the MySQL thread of + the user whose XA transaction should + be rolled back to savepoint */ + +/*****************************************************************//** +Sets a transaction savepoint. +@return always 0, that is, always succeeds */ +static +int +innobase_savepoint( +/*===============*/ + handlerton* hton, /*!< in/out: InnoDB handlerton */ + THD* thd, /*!< in: handle to the MySQL thread of + the user's XA transaction for which + we need to take a savepoint */ + void* savepoint); /*!< in: savepoint data */ + +/*****************************************************************//** +Release transaction savepoint name. +@return 0 if success, HA_ERR_NO_SAVEPOINT if no savepoint with the +given name */ +static +int +innobase_release_savepoint( +/*=======================*/ + handlerton* hton, /*!< in/out: handlerton for InnoDB */ + THD* thd, /*!< in: handle to the MySQL thread + of the user whose transaction's + savepoint should be released */ + void* savepoint); /*!< in: savepoint data */ + +/** Request notification of log writes */ +static void innodb_log_flush_request(void *cookie); + +/** Requests for log flushes */ +struct log_flush_request +{ + /** earlier request (for a smaller LSN) */ + log_flush_request *next; + /** parameter provided to innodb_log_flush_request() */ + void *cookie; + /** log sequence number that is being waited for */ + lsn_t lsn; +}; + +/** Buffer of pending innodb_log_flush_request() */ +alignas(CPU_LEVEL1_DCACHE_LINESIZE) static +struct +{ + /** first request */ + std::atomic<log_flush_request*> start; + /** last request */ + log_flush_request *end; + /** mutex protecting this object */ + mysql_mutex_t mutex; +} +log_requests; + +/** @brief Adjust some InnoDB startup parameters based on file contents +or innodb_page_size. */ +static +void +innodb_params_adjust(); + +/*******************************************************************//** +This function is used to prepare an X/Open XA distributed transaction. +@return 0 or error number */ +static +int +innobase_xa_prepare( +/*================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + THD* thd, /*!< in: handle to the MySQL thread of + the user whose XA transaction should + be prepared */ + bool all); /*!< in: true - prepare transaction + false - the current SQL statement + ended */ +/*******************************************************************//** +This function is used to recover X/Open XA distributed transactions. +@return number of prepared transactions stored in xid_list */ +static +int +innobase_xa_recover( +/*================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + XID* xid_list, /*!< in/out: prepared transactions */ + uint len); /*!< in: number of slots in xid_list */ +/*******************************************************************//** +This function is used to commit one X/Open XA distributed transaction +which is in the prepared state +@return 0 or error number */ +static +int +innobase_commit_by_xid( +/*===================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + XID* xid); /*!< in: X/Open XA transaction + identification */ + +/** Ignore FOREIGN KEY constraints that would be violated by DROP DATABASE */ +static ibool innodb_drop_database_ignore_fk(void*,void*) { return false; } + +/** FOREIGN KEY error reporting context for DROP DATABASE */ +struct innodb_drop_database_fk_report +{ + /** database name, with trailing '/' */ + const span<const char> name; + /** whether errors were found */ + bool violated; +}; + +/** Report FOREIGN KEY constraints that would be violated by DROP DATABASE +@return whether processing should continue */ +static ibool innodb_drop_database_fk(void *node, void *report) +{ + auto s= static_cast<sel_node_t*>(node); + auto r= static_cast<innodb_drop_database_fk_report*>(report); + const dfield_t *name= que_node_get_val(s->select_list); + ut_ad(name->type.mtype == DATA_VARCHAR); + + if (name->len == UNIV_SQL_NULL || name->len <= r->name.size() || + memcmp(static_cast<const char*>(name->data), r->name.data(), + r->name.size())) + return false; /* End of matches */ + + node= que_node_get_next(s->select_list); + const dfield_t *id= que_node_get_val(node); + ut_ad(id->type.mtype == DATA_VARCHAR); + ut_ad(!que_node_get_next(node)); + + if (id->len != UNIV_SQL_NULL) + sql_print_error("DROP DATABASE: table %.*s is referenced" + " by FOREIGN KEY %.*s", + static_cast<int>(name->len), + static_cast<const char*>(name->data), + static_cast<int>(id->len), + static_cast<const char*>(id->data)); + else + ut_ad("corrupted SYS_FOREIGN record" == 0); + + return true; +} + +/** After DROP DATABASE executed ha_innobase::delete_table() on all +tables that it was aware of, drop any leftover tables inside InnoDB. +@param path database path */ +static void innodb_drop_database(handlerton*, char *path) +{ + if (high_level_read_only) + return; + + ulint len= 0; + char *ptr; + + for (ptr= strend(path) - 2; ptr >= path && +#ifdef _WIN32 + *ptr != '\\' && +#endif + *ptr != '/'; ptr--) + len++; + + ptr++; + char *namebuf= static_cast<char*> + (my_malloc(PSI_INSTRUMENT_ME, len + 2, MYF(0))); + if (!namebuf) + return; + memcpy(namebuf, ptr, len); + namebuf[len] = '/'; + namebuf[len + 1] = '\0'; + +#ifdef _WIN32 + innobase_casedn_str(namebuf); +#endif /* _WIN32 */ + + THD * const thd= current_thd; + trx_t *trx= innobase_trx_allocate(thd); + dberr_t err= DB_SUCCESS; + + dict_sys.lock(SRW_LOCK_CALL); + + for (auto i= dict_sys.table_id_hash.n_cells; i--; ) + { + for (dict_table_t *next, *table= static_cast<dict_table_t*> + (dict_sys.table_id_hash.array[i].node); table; table= next) + { + ut_ad(table->cached); + next= table->id_hash; + if (strncmp(table->name.m_name, namebuf, len + 1)) + continue; + const auto n_handles= table->get_ref_count(); + const bool locks= !n_handles && lock_table_has_locks(table); + if (n_handles || locks) + { + err= DB_ERROR; + ib::error errmsg; + errmsg << "DROP DATABASE: cannot DROP TABLE " << table->name; + if (n_handles) + errmsg << " due to " << n_handles << " open handles"; + else + errmsg << " due to locks"; + continue; + } + dict_sys.remove(table); + } + } + + dict_sys.unlock(); + + dict_table_t *table_stats, *index_stats; + MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr; + table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false, + DICT_ERR_IGNORE_NONE); + if (table_stats) + { + dict_sys.freeze(SRW_LOCK_CALL); + table_stats= dict_acquire_mdl_shared<false>(table_stats, + thd, &mdl_table); + dict_sys.unfreeze(); + } + index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false, + DICT_ERR_IGNORE_NONE); + if (index_stats) + { + dict_sys.freeze(SRW_LOCK_CALL); + index_stats= dict_acquire_mdl_shared<false>(index_stats, + thd, &mdl_index); + dict_sys.unfreeze(); + } + + trx_start_for_ddl(trx); + + uint errors= 0; + char db[NAME_LEN + 1]; + strconvert(&my_charset_filename, namebuf, len, system_charset_info, db, + sizeof db, &errors); + if (!errors && table_stats && index_stats && + !strcmp(table_stats->name.m_name, TABLE_STATS_NAME) && + !strcmp(index_stats->name.m_name, INDEX_STATS_NAME) && + lock_table_for_trx(table_stats, trx, LOCK_X) == DB_SUCCESS && + lock_table_for_trx(index_stats, trx, LOCK_X) == DB_SUCCESS) + { + row_mysql_lock_data_dictionary(trx); + if (dict_stats_delete(db, trx)) + { + /* Ignore this error. Leaving garbage statistics behind is a + lesser evil. Carry on to try to remove any garbage tables. */ + trx->rollback(); + trx_start_for_ddl(trx); + } + row_mysql_unlock_data_dictionary(trx); + } + + if (err == DB_SUCCESS) + err= lock_sys_tables(trx); + row_mysql_lock_data_dictionary(trx); + + static const char drop_database[] = + "PROCEDURE DROP_DATABASE_PROC () IS\n" + "fk CHAR;\n" + "name CHAR;\n" + "tid CHAR;\n" + "iid CHAR;\n" + + "DECLARE FUNCTION fk_report;\n" + + "DECLARE CURSOR fkf IS\n" + "SELECT ID FROM SYS_FOREIGN WHERE ID >= :db FOR UPDATE;\n" + + "DECLARE CURSOR fkr IS\n" + "SELECT REF_NAME,ID FROM SYS_FOREIGN WHERE REF_NAME >= :db FOR UPDATE\n" + "ORDER BY REF_NAME;\n" + + "DECLARE CURSOR tab IS\n" + "SELECT ID,NAME FROM SYS_TABLES WHERE NAME >= :db FOR UPDATE;\n" + + "DECLARE CURSOR idx IS\n" + "SELECT ID FROM SYS_INDEXES WHERE TABLE_ID = tid FOR UPDATE;\n" + + "BEGIN\n" + + "OPEN fkf;\n" + "WHILE 1 = 1 LOOP\n" + " FETCH fkf INTO fk;\n" + " IF (SQL % NOTFOUND) THEN EXIT; END IF;\n" + " IF TO_BINARY(SUBSTR(fk, 0, LENGTH(:db)))<>TO_BINARY(:db)" + " THEN EXIT; END IF;\n" + " DELETE FROM SYS_FOREIGN_COLS WHERE TO_BINARY(ID)=TO_BINARY(fk);\n" + " DELETE FROM SYS_FOREIGN WHERE CURRENT OF fkf;\n" + "END LOOP;\n" + "CLOSE fkf;\n" + + "OPEN fkr;\n" + "FETCH fkr INTO fk_report();\n" + "CLOSE fkr;\n" + + "OPEN tab;\n" + "WHILE 1 = 1 LOOP\n" + " FETCH tab INTO tid,name;\n" + " IF (SQL % NOTFOUND) THEN EXIT; END IF;\n" + " IF TO_BINARY(SUBSTR(name, 0, LENGTH(:db))) <> TO_BINARY(:db)" + " THEN EXIT; END IF;\n" + " DELETE FROM SYS_COLUMNS WHERE TABLE_ID=tid;\n" + " DELETE FROM SYS_TABLES WHERE ID=tid;\n" + " OPEN idx;\n" + " WHILE 1 = 1 LOOP\n" + " FETCH idx INTO iid;\n" + " IF (SQL % NOTFOUND) THEN EXIT; END IF;\n" + " DELETE FROM SYS_FIELDS WHERE INDEX_ID=iid;\n" + " DELETE FROM SYS_INDEXES WHERE CURRENT OF idx;\n" + " END LOOP;\n" + " CLOSE idx;\n" + "END LOOP;\n" + "CLOSE tab;\n" + + "END;\n"; + + innodb_drop_database_fk_report report{{namebuf, len + 1}, false}; + + if (err == DB_SUCCESS) + { + pars_info_t* pinfo = pars_info_create(); + pars_info_bind_function(pinfo, "fk_report", trx->check_foreigns + ? innodb_drop_database_fk + : innodb_drop_database_ignore_fk, &report); + pars_info_add_str_literal(pinfo, "db", namebuf); + err= que_eval_sql(pinfo, drop_database, trx); + if (err == DB_SUCCESS && report.violated) + err= DB_CANNOT_DROP_CONSTRAINT; + } + + const trx_id_t trx_id= trx->id; + + if (err != DB_SUCCESS) + { + trx->rollback(); + namebuf[len] = '\0'; + ib::error() << "DROP DATABASE " << namebuf << ": " << err; + } + else + trx->commit(); + + if (table_stats) + dict_table_close(table_stats, true, thd, mdl_table); + if (index_stats) + dict_table_close(index_stats, true, thd, mdl_index); + row_mysql_unlock_data_dictionary(trx); + + trx->free(); + + if (err == DB_SUCCESS) + { + /* Eventually after the DELETE FROM SYS_INDEXES was committed, + purge would invoke dict_drop_index_tree() to delete the associated + tablespaces. Because the SQL layer expects the directory to be empty, + we will "manually" purge the tablespaces that belong to the + records that we delete-marked. */ + + dfield_t dfield; + dtuple_t tuple{ + 0,1,1,&dfield,0,nullptr +#ifdef UNIV_DEBUG + , DATA_TUPLE_MAGIC_N +#endif + }; + dict_index_t* sys_index= UT_LIST_GET_FIRST(dict_sys.sys_tables->indexes); + btr_pcur_t pcur; + namebuf[len++]= '/'; + dfield_set_data(&dfield, namebuf, len); + dict_index_copy_types(&tuple, sys_index, 1); + std::vector<pfs_os_file_t> to_close; + std::vector<uint32_t> space_ids; + mtr_t mtr; + mtr.start(); + pcur.btr_cur.page_cur.index = sys_index; + err= btr_pcur_open_on_user_rec(&tuple, BTR_SEARCH_LEAF, &pcur, &mtr); + if (err != DB_SUCCESS) + goto err_exit; + + for (; btr_pcur_is_on_user_rec(&pcur); + btr_pcur_move_to_next_user_rec(&pcur, &mtr)) + { + const rec_t *rec= btr_pcur_get_rec(&pcur); + if (rec_get_n_fields_old(rec) != DICT_NUM_FIELDS__SYS_TABLES) + { + ut_ad("corrupted SYS_TABLES record" == 0); + break; + } + if (!rec_get_deleted_flag(rec, false)) + continue; + ulint flen; + static_assert(DICT_FLD__SYS_TABLES__NAME == 0, "compatibility"); + rec_get_nth_field_offs_old(rec, 0, &flen); + if (flen == UNIV_SQL_NULL || flen <= len || memcmp(rec, namebuf, len)) + /* We ran out of tables that had existed in the database. */ + break; + const byte *db_trx_id= + rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__DB_TRX_ID, &flen); + if (flen != 6) + { + ut_ad("corrupted SYS_TABLES.SPACE" == 0); + break; + } + if (mach_read_from_6(db_trx_id) != trx_id) + /* This entry was modified by some other transaction than us. + Unfortunately, because SYS_TABLES.NAME is the PRIMARY KEY, + we cannot distinguish RENAME and DROP here. It is possible + that the table had been renamed to some other database. */ + continue; + const byte *s= + rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__SPACE, &flen); + if (flen != 4) + ut_ad("corrupted SYS_TABLES.SPACE" == 0); + else if (uint32_t space_id= mach_read_from_4(s)) + { + space_ids.emplace_back(space_id); + pfs_os_file_t detached= fil_delete_tablespace(space_id); + if (detached != OS_FILE_CLOSED) + to_close.emplace_back(detached); + } + } + err_exit: + mtr.commit(); + for (pfs_os_file_t detached : to_close) + os_file_close(detached); + for (const auto id : space_ids) + ibuf_delete_for_discarded_space(id); + + /* Any changes must be persisted before we return. */ + log_write_up_to(mtr.commit_lsn(), true); + } + + my_free(namebuf); +} + +/** Shut down the InnoDB storage engine. +@return 0 */ +static +int +innobase_end(handlerton*, ha_panic_function); + +/*****************************************************************//** +Creates an InnoDB transaction struct for the thd if it does not yet have one. +Starts a new InnoDB transaction if a transaction is not yet started. And +assigns a new snapshot for a consistent read if the transaction does not yet +have one. +@return 0 */ +static +int +innobase_start_trx_and_assign_read_view( +/*====================================*/ + handlerton* hton, /* in: InnoDB handlerton */ + THD* thd); /* in: MySQL thread handle of the + user for whom the transaction should + be committed */ + +/** Flush InnoDB redo logs to the file system. +@return false */ +static bool innobase_flush_logs(handlerton*) +{ + if (!srv_read_only_mode && srv_flush_log_at_trx_commit) + /* Write any outstanding redo log. Durably if + innodb_flush_log_at_trx_commit=1. */ + log_buffer_flush_to_disk(srv_flush_log_at_trx_commit == 1); + return false; +} + +/************************************************************************//** +Implements the SHOW ENGINE INNODB STATUS command. Sends the output of the +InnoDB Monitor to the client. +@return 0 on success */ +static +int +innodb_show_status( +/*===============*/ + handlerton* hton, /*!< in: the innodb handlerton */ + THD* thd, /*!< in: the MySQL query thread of + the caller */ + stat_print_fn* stat_print); +/************************************************************************//** +Return 0 on success and non-zero on failure. Note: the bool return type +seems to be abused here, should be an int. */ +static +bool +innobase_show_status( +/*=================*/ + handlerton* hton, /*!< in: the innodb handlerton */ + THD* thd, /*!< in: the MySQL query thread of + the caller */ + stat_print_fn* stat_print, + enum ha_stat_type stat_type); + +/** After ALTER TABLE, recompute statistics. */ +inline void ha_innobase::reload_statistics() +{ + if (dict_table_t *table= m_prebuilt ? m_prebuilt->table : nullptr) + { + if (table->is_readable()) + dict_stats_init(table); + else + table->stat_initialized= 1; + } +} + +/** After ALTER TABLE, recompute statistics. */ +static int innodb_notify_tabledef_changed(handlerton *, + LEX_CSTRING *, LEX_CSTRING *, + LEX_CUSTRING *, LEX_CUSTRING *, + handler *handler) +{ + DBUG_ENTER("innodb_notify_tabledef_changed"); + if (handler) + static_cast<ha_innobase*>(handler)->reload_statistics(); + DBUG_RETURN(0); +} + +/****************************************************************//** +Parse and enable InnoDB monitor counters during server startup. +User can enable monitor counters/groups by specifying +"loose-innodb_monitor_enable = monitor_name1;monitor_name2..." +in server configuration file or at the command line. */ +static +void +innodb_enable_monitor_at_startup( +/*=============================*/ + char* str); /*!< in: monitor counter enable list */ + +#ifdef MYSQL_STORE_FTS_DOC_ID +/** Store doc_id value into FTS_DOC_ID field +@param[in,out] tbl table containing FULLTEXT index +@param[in] doc_id FTS_DOC_ID value */ +static +void +innobase_fts_store_docid( + TABLE* tbl, + ulonglong doc_id) +{ + my_bitmap_map* old_map + = dbug_tmp_use_all_columns(tbl, tbl->write_set); + + tbl->fts_doc_id_field->store(static_cast<longlong>(doc_id), true); + + dbug_tmp_restore_column_map(tbl->write_set, old_map); +} +#endif + +/*******************************************************************//** +Function for constructing an InnoDB table handler instance. */ +static +handler* +innobase_create_handler( +/*====================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + TABLE_SHARE* table, + MEM_ROOT* mem_root) +{ + return(new (mem_root) ha_innobase(hton, table)); +} + +/* General functions */ + +/** Check that a page_size is correct for InnoDB. +If correct, set the associated page_size_shift which is the power of 2 +for this page size. +@param[in] page_size Page Size to evaluate +@return an associated page_size_shift if valid, 0 if invalid. */ +inline uint32_t innodb_page_size_validate(ulong page_size) +{ + DBUG_ENTER("innodb_page_size_validate"); + + for (uint32_t n = UNIV_PAGE_SIZE_SHIFT_MIN; + n <= UNIV_PAGE_SIZE_SHIFT_MAX; + n++) { + if (page_size == static_cast<ulong>(1 << n)) { + DBUG_RETURN(n); + } + } + + DBUG_RETURN(0); +} + +/******************************************************************//** +Returns true if transaction should be flagged as read-only. +@return true if the thd is marked as read-only */ +bool +thd_trx_is_read_only( +/*=================*/ + THD* thd) /*!< in: thread handle */ +{ + return(thd != 0 && thd_tx_is_read_only(thd)); +} + +static MYSQL_THDVAR_BOOL(background_thread, + PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_NOSYSVAR, + "Internal (not user visible) flag to mark " + "background purge threads", NULL, NULL, 0); + +/** Create a MYSQL_THD for a background thread and mark it as such. +@param name thread info for SHOW PROCESSLIST +@return new MYSQL_THD */ +MYSQL_THD innobase_create_background_thd(const char* name) +{ + MYSQL_THD thd= create_background_thd(); + thd_proc_info(thd, name); + THDVAR(thd, background_thread) = true; + return thd; +} + + +/** Close opened tables, free memory, delete items for a MYSQL_THD. +@param[in] thd MYSQL_THD to reset */ +void +innobase_reset_background_thd(MYSQL_THD thd) +{ + if (!thd) { + thd = current_thd; + } + + ut_ad(thd); + ut_ad(THDVAR(thd, background_thread)); + + /* background purge thread */ + const char *proc_info= thd_proc_info(thd, "reset"); + reset_thd(thd); + thd_proc_info(thd, proc_info); +} + + +/******************************************************************//** +Check if the transaction is an auto-commit transaction. TRUE also +implies that it is a SELECT (read-only) transaction. +@return true if the transaction is an auto commit read-only transaction. */ +ibool +thd_trx_is_auto_commit( +/*===================*/ + THD* thd) /*!< in: thread handle, can be NULL */ +{ + return(thd != NULL + && !thd_test_options( + thd, + OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) + && thd_sql_command(thd) == SQLCOM_SELECT); +} + +/******************************************************************//** +Returns the NUL terminated value of glob_hostname. +@return pointer to glob_hostname. */ +const char* +server_get_hostname() +/*=================*/ +{ + return(glob_hostname); +} + +/******************************************************************//** +Returns true if the transaction this thread is processing has edited +non-transactional tables. Used by the deadlock detector when deciding +which transaction to rollback in case of a deadlock - we try to avoid +rolling back transactions that have edited non-transactional tables. +@return true if non-transactional tables have been edited */ +ibool +thd_has_edited_nontrans_tables( +/*===========================*/ + THD* thd) /*!< in: thread handle */ +{ + return((ibool) thd_non_transactional_update(thd)); +} + +/******************************************************************//** +Returns the lock wait timeout for the current connection. +@return the lock wait timeout, in seconds */ +uint& +thd_lock_wait_timeout( +/*==================*/ + THD* thd) /*!< in: thread handle, or NULL to query + the global innodb_lock_wait_timeout */ +{ + /* According to <mysql/plugin.h>, passing thd == NULL + returns the global value of the session variable. */ + return(THDVAR(thd, lock_wait_timeout)); +} + +/** Get the value of innodb_tmpdir. +@param[in] thd thread handle, or NULL to query + the global innodb_tmpdir. +@retval NULL if innodb_tmpdir="" */ +const char *thd_innodb_tmpdir(THD *thd) +{ + const char* tmp_dir = THDVAR(thd, tmpdir); + + if (tmp_dir != NULL && *tmp_dir == '\0') { + tmp_dir = NULL; + } + + return(tmp_dir); +} + +/** Obtain the InnoDB transaction of a MySQL thread. +@param[in,out] thd thread handle +@return reference to transaction pointer */ +static trx_t* thd_to_trx(THD* thd) +{ + return reinterpret_cast<trx_t*>(thd_get_ha_data(thd, innodb_hton_ptr)); +} + +#ifdef WITH_WSREP +/********************************************************************//** +Obtain the InnoDB transaction id of a MySQL thread. +@return transaction id */ +__attribute__((warn_unused_result, nonnull)) +ulonglong +thd_to_trx_id( + THD* thd) /*!< in: MySQL thread */ +{ + return(thd_to_trx(thd)->id); +} + +Atomic_relaxed<bool> wsrep_sst_disable_writes; + +static void sst_disable_innodb_writes() +{ + const uint old_count= srv_n_fil_crypt_threads; + fil_crypt_set_thread_cnt(0); + srv_n_fil_crypt_threads= old_count; + + wsrep_sst_disable_writes= true; + dict_stats_shutdown(); + purge_sys.stop(); + /* We are holding a global MDL thanks to FLUSH TABLES WITH READ LOCK. + + That will prevent any writes from arriving into InnoDB, but it will + not prevent writes of modified pages from the buffer pool, or log + checkpoints. + + Let us perform a log checkpoint to ensure that the entire buffer + pool is clean, so that no writes to persistent files will be + possible during the snapshot, and to guarantee that no crash + recovery will be necessary when starting up on the snapshot. */ + log_make_checkpoint(); + /* If any FILE_MODIFY records were written by the checkpoint, an + extra write of a FILE_CHECKPOINT record could still be invoked by + buf_flush_page_cleaner(). Let us prevent that by invoking another + checkpoint (which will write the FILE_CHECKPOINT record). */ + log_make_checkpoint(); + ut_d(recv_no_log_write= true); + /* If this were not a no-op, an assertion would fail due to + recv_no_log_write. */ + ut_d(log_make_checkpoint()); +} + +static void sst_enable_innodb_writes() +{ + ut_ad(recv_no_log_write); + ut_d(recv_no_log_write= false); + dict_stats_start(); + purge_sys.resume(); + wsrep_sst_disable_writes= false; + const uint old_count= srv_n_fil_crypt_threads; + srv_n_fil_crypt_threads= 0; + fil_crypt_set_thread_cnt(old_count); +} + +static void innodb_disable_internal_writes(bool disable) +{ + if (disable) + sst_disable_innodb_writes(); + else + sst_enable_innodb_writes(); +} + +static void wsrep_abort_transaction(handlerton *, THD *, THD *, my_bool) + __attribute__((nonnull)); +static int innobase_wsrep_set_checkpoint(handlerton *hton, const XID *xid); +static int innobase_wsrep_get_checkpoint(handlerton* hton, XID* xid); +#endif /* WITH_WSREP */ + +#define normalize_table_name(a,b) \ + normalize_table_name_c_low(a,b,IF_WIN(true,false)) + +ulonglong ha_innobase::table_version() const +{ + /* This is either "garbage" or something that was assigned + on a successful ha_innobase::prepare_inplace_alter_table(). */ + return m_prebuilt->trx_id; +} + +#ifdef UNIV_DEBUG +/** whether the DDL log recovery has been completed */ +static bool ddl_recovery_done; +#endif + +static int innodb_check_version(handlerton *hton, const char *path, + const LEX_CUSTRING *version, + ulonglong create_id) +{ + DBUG_ENTER("innodb_check_version"); + DBUG_ASSERT(hton == innodb_hton_ptr); + ut_ad(!ddl_recovery_done); + + if (!create_id) + DBUG_RETURN(0); + + char norm_path[FN_REFLEN]; + normalize_table_name(norm_path, path); + + if (dict_table_t *table= dict_table_open_on_name(norm_path, false, + DICT_ERR_IGNORE_NONE)) + { + const trx_id_t trx_id= table->def_trx_id; + DBUG_ASSERT(trx_id <= create_id); + dict_table_close(table); + DBUG_PRINT("info", ("create_id: %llu trx_id: %llu", create_id, trx_id)); + DBUG_RETURN(create_id != trx_id); + } + else + DBUG_RETURN(2); +} + +/** Drop any garbage intermediate tables that existed in the system +after a backup was restored. + +In a final phase of Mariabackup, the commit of DDL operations is blocked, +and those DDL operations will have to be rolled back. Because the +normal DDL recovery will not run due to the lack of the log file, +at least some #sql-alter- garbage tables may remain in the InnoDB +data dictionary (while the data files themselves are missing). +We will attempt to drop the tables here. */ +static void drop_garbage_tables_after_restore() +{ + btr_pcur_t pcur; + mtr_t mtr; + trx_t *trx= trx_create(); + + ut_ad(!purge_sys.enabled()); + ut_d(purge_sys.stop_FTS()); + + mtr.start(); + if (pcur.open_leaf(true, dict_sys.sys_tables->indexes.start, BTR_SEARCH_LEAF, + &mtr) != DB_SUCCESS) + goto all_fail; + for (;;) + { + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + + if (!btr_pcur_is_on_user_rec(&pcur)) + break; + + const rec_t *rec= btr_pcur_get_rec(&pcur); + if (rec_get_deleted_flag(rec, 0)) + continue; + + static_assert(DICT_FLD__SYS_TABLES__NAME == 0, "compatibility"); + size_t len; + if (rec_get_1byte_offs_flag(rec)) + { + len= rec_1_get_field_end_info(rec, 0); + if (len & REC_1BYTE_SQL_NULL_MASK) + continue; /* corrupted SYS_TABLES.NAME */ + } + else + { + len= rec_2_get_field_end_info(rec, 0); + static_assert(REC_2BYTE_EXTERN_MASK == 16384, "compatibility"); + if (len >= REC_2BYTE_EXTERN_MASK) + continue; /* corrupted SYS_TABLES.NAME */ + } + + if (len < tmp_file_prefix_length) + continue; + if (const char *f= static_cast<const char*> + (memchr(rec, '/', len - tmp_file_prefix_length))) + { + if (memcmp(f + 1, tmp_file_prefix, tmp_file_prefix_length)) + continue; + } + else + continue; + + btr_pcur_store_position(&pcur, &mtr); + btr_pcur_commit_specify_mtr(&pcur, &mtr); + + trx_start_for_ddl(trx); + std::vector<pfs_os_file_t> deleted; + dberr_t err= DB_TABLE_NOT_FOUND; + row_mysql_lock_data_dictionary(trx); + + if (dict_table_t *table= dict_sys.load_table + ({reinterpret_cast<const char*>(pcur.old_rec), len}, + DICT_ERR_IGNORE_DROP)) + { + table->acquire(); + row_mysql_unlock_data_dictionary(trx); + err= lock_table_for_trx(table, trx, LOCK_X); + if (err == DB_SUCCESS && + (table->flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS))) + { + fts_optimize_remove_table(table); + err= fts_lock_tables(trx, *table); + } + if (err == DB_SUCCESS) + err= lock_sys_tables(trx); + row_mysql_lock_data_dictionary(trx); + table->release(); + + if (err == DB_SUCCESS) + err= trx->drop_table(*table); + if (err != DB_SUCCESS) + goto fail; + trx->commit(deleted); + } + else + { +fail: + trx->rollback(); + sql_print_error("InnoDB: cannot drop %.*s: %s", + static_cast<int>(len), pcur.old_rec, ut_strerr(err)); + } + + row_mysql_unlock_data_dictionary(trx); + for (pfs_os_file_t d : deleted) + os_file_close(d); + + mtr.start(); + if (pcur.restore_position(BTR_SEARCH_LEAF, &mtr) == btr_pcur_t::CORRUPTED) + break; + } + +all_fail: + mtr.commit(); + trx->free(); + ut_free(pcur.old_rec_buf); + ut_d(purge_sys.resume_FTS()); +} + +static void innodb_ddl_recovery_done(handlerton*) +{ + ut_ad(!ddl_recovery_done); + ut_d(ddl_recovery_done= true); + if (!srv_read_only_mode && srv_operation <= SRV_OPERATION_EXPORT_RESTORED && + srv_force_recovery < SRV_FORCE_NO_BACKGROUND) + { + if (srv_start_after_restore && !high_level_read_only) + drop_garbage_tables_after_restore(); + srv_init_purge_tasks(); + } +} + +/********************************************************************//** +Converts an InnoDB error code to a MySQL error code and also tells to MySQL +about a possible transaction rollback inside InnoDB caused by a lock wait +timeout or a deadlock. +@return MySQL error code */ +static int +convert_error_code_to_mysql( +/*========================*/ + dberr_t error, /*!< in: InnoDB error code */ + ulint flags, /*!< in: InnoDB table flags, or 0 */ + THD* thd) /*!< in: user thread handle or NULL */ +{ + switch (error) { + case DB_SUCCESS: + return(0); + + case DB_INTERRUPTED: + return(HA_ERR_ABORTED_BY_USER); + + case DB_FOREIGN_EXCEED_MAX_CASCADE: + ut_ad(thd); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + HA_ERR_ROW_IS_REFERENCED, + "InnoDB: Cannot delete/update " + "rows with cascading foreign key " + "constraints that exceed max " + "depth of %d. Please " + "drop extra constraints and try " + "again", FK_MAX_CASCADE_DEL); + return(HA_ERR_FK_DEPTH_EXCEEDED); + + case DB_CANT_CREATE_GEOMETRY_OBJECT: + my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0)); + return(HA_ERR_NULL_IN_SPATIAL); + + case DB_ERROR: + default: + return(HA_ERR_GENERIC); /* unspecified error */ + + case DB_DUPLICATE_KEY: + /* Be cautious with returning this error, since + mysql could re-enter the storage layer to get + duplicated key info, the operation requires a + valid table handle and/or transaction information, + which might not always be available in the error + handling stage. */ + return(HA_ERR_FOUND_DUPP_KEY); + + case DB_READ_ONLY: + return(HA_ERR_TABLE_READONLY); + + case DB_FOREIGN_DUPLICATE_KEY: + return(HA_ERR_FOREIGN_DUPLICATE_KEY); + + case DB_MISSING_HISTORY: + return(HA_ERR_TABLE_DEF_CHANGED); + + case DB_RECORD_NOT_FOUND: + return(HA_ERR_NO_ACTIVE_RECORD); + + case DB_DEADLOCK: + /* Since we rolled back the whole transaction, we must + tell it also to MySQL so that MySQL knows to empty the + cached binlog for this transaction */ + + if (thd != NULL) { + thd_mark_transaction_to_rollback(thd, 1); + } + + return(HA_ERR_LOCK_DEADLOCK); + + case DB_LOCK_WAIT_TIMEOUT: + /* Starting from 5.0.13, we let MySQL just roll back the + latest SQL statement in a lock wait timeout. Previously, we + rolled back the whole transaction. */ + + if (thd) { + thd_mark_transaction_to_rollback( + thd, innobase_rollback_on_timeout); + } + + return(HA_ERR_LOCK_WAIT_TIMEOUT); + + case DB_NO_REFERENCED_ROW: + return(HA_ERR_NO_REFERENCED_ROW); + + case DB_ROW_IS_REFERENCED: + return(HA_ERR_ROW_IS_REFERENCED); + + case DB_NO_FK_ON_S_BASE_COL: + case DB_CANNOT_ADD_CONSTRAINT: + case DB_CHILD_NO_INDEX: + case DB_PARENT_NO_INDEX: + return(HA_ERR_CANNOT_ADD_FOREIGN); + + case DB_CANNOT_DROP_CONSTRAINT: + + return(HA_ERR_ROW_IS_REFERENCED); /* TODO: This is a bit + misleading, a new MySQL error + code should be introduced */ + + case DB_CORRUPTION: + case DB_PAGE_CORRUPTED: + return(HA_ERR_CRASHED); + + case DB_OUT_OF_FILE_SPACE: + return(HA_ERR_RECORD_FILE_FULL); + + case DB_TEMP_FILE_WRITE_FAIL: + my_error(ER_GET_ERRMSG, MYF(0), + DB_TEMP_FILE_WRITE_FAIL, + ut_strerr(DB_TEMP_FILE_WRITE_FAIL), + "InnoDB"); + return(HA_ERR_INTERNAL_ERROR); + + case DB_TABLE_NOT_FOUND: + return(HA_ERR_NO_SUCH_TABLE); + + case DB_DECRYPTION_FAILED: + return(HA_ERR_DECRYPTION_FAILED); + + case DB_TABLESPACE_NOT_FOUND: + return(HA_ERR_TABLESPACE_MISSING); + + case DB_TOO_BIG_RECORD: { + /* If prefix is true then a 768-byte prefix is stored + locally for BLOB fields. Refer to dict_table_get_format(). + We limit max record size to 16k for 64k page size. */ + bool prefix = !DICT_TF_HAS_ATOMIC_BLOBS(flags); + bool comp = !!(flags & DICT_TF_COMPACT); + ulint free_space = page_get_free_space_of_empty(comp) / 2; + + if (free_space >= ulint(comp ? COMPRESSED_REC_MAX_DATA_SIZE : + REDUNDANT_REC_MAX_DATA_SIZE)) { + free_space = (comp ? COMPRESSED_REC_MAX_DATA_SIZE : + REDUNDANT_REC_MAX_DATA_SIZE) - 1; + } + + my_printf_error(ER_TOO_BIG_ROWSIZE, + "Row size too large (> " ULINTPF "). Changing some columns " + "to TEXT or BLOB %smay help. In current row " + "format, BLOB prefix of %d bytes is stored inline.", + MYF(0), + free_space, + prefix + ? "or using ROW_FORMAT=DYNAMIC or" + " ROW_FORMAT=COMPRESSED " + : "", + prefix + ? DICT_MAX_FIXED_COL_LEN + : 0); + return(HA_ERR_TO_BIG_ROW); + } + + case DB_TOO_BIG_INDEX_COL: + my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), + (ulong) DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(flags)); + return(HA_ERR_INDEX_COL_TOO_LONG); + + case DB_NO_SAVEPOINT: + return(HA_ERR_NO_SAVEPOINT); + + case DB_LOCK_TABLE_FULL: + /* Since we rolled back the whole transaction, we must + tell it also to MySQL so that MySQL knows to empty the + cached binlog for this transaction */ + + if (thd) { + thd_mark_transaction_to_rollback(thd, 1); + } + + return(HA_ERR_LOCK_TABLE_FULL); + + case DB_FTS_INVALID_DOCID: + return(HA_FTS_INVALID_DOCID); + case DB_FTS_EXCEED_RESULT_CACHE_LIMIT: + return(HA_ERR_OUT_OF_MEM); + case DB_TOO_MANY_CONCURRENT_TRXS: + return(HA_ERR_TOO_MANY_CONCURRENT_TRXS); + case DB_UNSUPPORTED: + return(HA_ERR_UNSUPPORTED); + case DB_INDEX_CORRUPT: + return(HA_ERR_INDEX_CORRUPT); + case DB_UNDO_RECORD_TOO_BIG: + return(HA_ERR_UNDO_REC_TOO_BIG); + case DB_OUT_OF_MEMORY: + return(HA_ERR_OUT_OF_MEM); + case DB_TABLESPACE_EXISTS: + return(HA_ERR_TABLESPACE_EXISTS); + case DB_TABLESPACE_DELETED: + return(HA_ERR_TABLESPACE_MISSING); + case DB_IDENTIFIER_TOO_LONG: + return(HA_ERR_INTERNAL_ERROR); + case DB_TABLE_CORRUPT: + return(HA_ERR_TABLE_CORRUPT); + case DB_FTS_TOO_MANY_WORDS_IN_PHRASE: + return(HA_ERR_FTS_TOO_MANY_WORDS_IN_PHRASE); + case DB_COMPUTE_VALUE_FAILED: + return(HA_ERR_GENERIC); // impossible + } +} + +/*************************************************************//** +Prints info of a THD object (== user session thread) to the given file. */ +void +innobase_mysql_print_thd( +/*=====================*/ + FILE* f, /*!< in: output stream */ + THD* thd, /*!< in: MySQL THD object */ + uint max_query_len) /*!< in: max query length to print, or 0 to + use the default max length */ +{ + char buffer[1024]; + + fputs(thd_get_error_context_description(thd, buffer, sizeof buffer, + max_query_len), f); + putc('\n', f); +} + +/******************************************************************//** +Get the variable length bounds of the given character set. */ +static void +innobase_get_cset_width( +/*====================*/ + ulint cset, /*!< in: MySQL charset-collation code */ + unsigned*mbminlen, /*!< out: minimum length of a char (in bytes) */ + unsigned*mbmaxlen) /*!< out: maximum length of a char (in bytes) */ +{ + CHARSET_INFO* cs; + ut_ad(cset <= MAX_CHAR_COLL_NUM); + ut_ad(mbminlen); + ut_ad(mbmaxlen); + + cs = cset ? get_charset((uint)cset, MYF(MY_WME)) : NULL; + if (cs) { + *mbminlen = cs->mbminlen; + *mbmaxlen = cs->mbmaxlen; + ut_ad(*mbminlen < DATA_MBMAX); + ut_ad(*mbmaxlen < DATA_MBMAX); + } else { + THD* thd = current_thd; + + if (thd && thd_sql_command(thd) == SQLCOM_DROP_TABLE) { + + /* Fix bug#46256: allow tables to be dropped if the + collation is not found, but issue a warning. */ + if (cset != 0) { + + sql_print_warning( + "Unknown collation #" ULINTPF ".", + cset); + } + } else { + + ut_a(cset == 0); + } + + *mbminlen = *mbmaxlen = 0; + } +} + +/*********************************************************************//** +Compute the mbminlen and mbmaxlen members of a data type structure. */ +void +dtype_get_mblen( +/*============*/ + ulint mtype, /*!< in: main type */ + ulint prtype, /*!< in: precise type (and collation) */ + unsigned*mbminlen, /*!< out: minimum length of a + multi-byte character */ + unsigned*mbmaxlen) /*!< out: maximum length of a + multi-byte character */ +{ + if (dtype_is_string_type(mtype)) { + innobase_get_cset_width(dtype_get_charset_coll(prtype), + mbminlen, mbmaxlen); + ut_ad(*mbminlen <= *mbmaxlen); + ut_ad(*mbminlen < DATA_MBMAX); + ut_ad(*mbmaxlen < DATA_MBMAX); + } else { + *mbminlen = *mbmaxlen = 0; + } +} + +/******************************************************************//** +Converts an identifier to a table name. */ +void +innobase_convert_from_table_id( +/*===========================*/ + CHARSET_INFO* cs, /*!< in: the 'from' character set */ + char* to, /*!< out: converted identifier */ + const char* from, /*!< in: identifier to convert */ + ulint len) /*!< in: length of 'to', in bytes */ +{ + uint errors; + + strconvert(cs, from, FN_REFLEN, &my_charset_filename, to, (uint) len, &errors); +} + +/********************************************************************** +Check if the length of the identifier exceeds the maximum allowed. +return true when length of identifier is too long. */ +my_bool +innobase_check_identifier_length( +/*=============================*/ + const char* id) /* in: FK identifier to check excluding the + database portion. */ +{ + int well_formed_error = 0; + CHARSET_INFO *cs = system_charset_info; + DBUG_ENTER("innobase_check_identifier_length"); + + size_t len = my_well_formed_length( + cs, id, id + strlen(id), + NAME_CHAR_LEN, &well_formed_error); + + if (well_formed_error || len == NAME_CHAR_LEN) { + my_error(ER_TOO_LONG_IDENT, MYF(0), id); + DBUG_RETURN(true); + } + DBUG_RETURN(false); +} + +/******************************************************************//** +Converts an identifier to UTF-8. */ +void +innobase_convert_from_id( +/*=====================*/ + CHARSET_INFO* cs, /*!< in: the 'from' character set */ + char* to, /*!< out: converted identifier */ + const char* from, /*!< in: identifier to convert */ + ulint len) /*!< in: length of 'to', in bytes */ +{ + uint errors; + + strconvert(cs, from, FN_REFLEN, system_charset_info, to, (uint) len, &errors); +} + +/******************************************************************//** +Compares NUL-terminated UTF-8 strings case insensitively. +@return 0 if a=b, <0 if a<b, >1 if a>b */ +int +innobase_strcasecmp( +/*================*/ + const char* a, /*!< in: first string to compare */ + const char* b) /*!< in: second string to compare */ +{ + if (!a) { + if (!b) { + return(0); + } else { + return(-1); + } + } else if (!b) { + return(1); + } + + return(my_strcasecmp(system_charset_info, a, b)); +} + +/******************************************************************//** +Compares NUL-terminated UTF-8 strings case insensitively. The +second string contains wildcards. +@return 0 if a match is found, 1 if not */ +static +int +innobase_wildcasecmp( +/*=================*/ + const char* a, /*!< in: string to compare */ + const char* b) /*!< in: wildcard string to compare */ +{ + return(wild_case_compare(system_charset_info, a, b)); +} + +/** Strip dir name from a full path name and return only the file name +@param[in] path_name full path name +@return file name or "null" if no file name */ +const char* +innobase_basename( + const char* path_name) +{ + const char* name = base_name(path_name); + + return((name) ? name : "null"); +} + +/******************************************************************//** +Makes all characters in a NUL-terminated UTF-8 string lower case. */ +void +innobase_casedn_str( +/*================*/ + char* a) /*!< in/out: string to put in lower case */ +{ + my_casedn_str(system_charset_info, a); +} + +/** Determines the current SQL statement. +Thread unsafe, can only be called from the thread owning the THD. +@param[in] thd MySQL thread handle +@param[out] length Length of the SQL statement +@return SQL statement string */ +const char* +innobase_get_stmt_unsafe( + THD* thd, + size_t* length) +{ + if (const LEX_STRING *stmt = thd_query_string(thd)) { + *length = stmt->length; + return stmt->str; + } + + *length = 0; + return NULL; +} + +/** + Test a file path whether it is same as mysql data directory path. + + @param path null terminated character string + + @return + @retval TRUE The path is different from mysql data directory. + @retval FALSE The path is same as mysql data directory. +*/ +static bool is_mysql_datadir_path(const char *path) +{ + if (path == NULL) + return false; + + char mysql_data_dir[FN_REFLEN], path_dir[FN_REFLEN]; + convert_dirname(path_dir, path, NullS); + convert_dirname(mysql_data_dir, mysql_unpacked_real_data_home, NullS); + size_t mysql_data_home_len= dirname_length(mysql_data_dir); + size_t path_len = dirname_length(path_dir); + + if (path_len < mysql_data_home_len) + return true; + + if (!lower_case_file_system) + return(memcmp(mysql_data_dir, path_dir, mysql_data_home_len)); + + return(files_charset_info->strnncoll((uchar *) path_dir, path_len, + (uchar *) mysql_data_dir, + mysql_data_home_len, + TRUE)); +} + +/*********************************************************************//** +Wrapper around MySQL's copy_and_convert function. +@return number of bytes copied to 'to' */ +static +ulint +innobase_convert_string( +/*====================*/ + void* to, /*!< out: converted string */ + ulint to_length, /*!< in: number of bytes reserved + for the converted string */ + CHARSET_INFO* to_cs, /*!< in: character set to convert to */ + const void* from, /*!< in: string to convert */ + ulint from_length, /*!< in: number of bytes to convert */ + CHARSET_INFO* from_cs, /*!< in: character set to convert + from */ + uint* errors) /*!< out: number of errors encountered + during the conversion */ +{ + return(copy_and_convert( + (char*) to, (uint32) to_length, to_cs, + (const char*) from, (uint32) from_length, from_cs, + errors)); +} + +/*******************************************************************//** +Formats the raw data in "data" (in InnoDB on-disk format) that is of +type DATA_(CHAR|VARCHAR|MYSQL|VARMYSQL) using "charset_coll" and writes +the result to "buf". The result is converted to "system_charset_info". +Not more than "buf_size" bytes are written to "buf". +The result is always NUL-terminated (provided buf_size > 0) and the +number of bytes that were written to "buf" is returned (including the +terminating NUL). +@return number of bytes that were written */ +ulint +innobase_raw_format( +/*================*/ + const char* data, /*!< in: raw data */ + ulint data_len, /*!< in: raw data length + in bytes */ + ulint charset_coll, /*!< in: charset collation */ + char* buf, /*!< out: output buffer */ + ulint buf_size) /*!< in: output buffer size + in bytes */ +{ + /* XXX we use a hard limit instead of allocating + but_size bytes from the heap */ + CHARSET_INFO* data_cs; + char buf_tmp[8192]; + ulint buf_tmp_used; + uint num_errors; + + data_cs = all_charsets[charset_coll]; + + buf_tmp_used = innobase_convert_string(buf_tmp, sizeof(buf_tmp), + system_charset_info, + data, data_len, data_cs, + &num_errors); + + return(ut_str_sql_format(buf_tmp, buf_tmp_used, buf, buf_size)); +} + +/* +The helper function nlz(x) calculates the number of leading zeros +in the binary representation of the number "x", either using a +built-in compiler function or a substitute trick based on the use +of the multiplication operation and a table indexed by the prefix +of the multiplication result: +*/ +#ifdef __GNUC__ +#define nlz(x) __builtin_clzll(x) +#elif defined(_MSC_VER) && !defined(_M_CEE_PURE) && \ + (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64)) +#ifndef __INTRIN_H_ +#pragma warning(push, 4) +#pragma warning(disable: 4255 4668) +#include <intrin.h> +#pragma warning(pop) +#endif +__forceinline unsigned int nlz (ulonglong x) +{ +#if defined(_M_IX86) || defined(_M_X64) + unsigned long n; +#ifdef _M_X64 + _BitScanReverse64(&n, x); + return (unsigned int) n ^ 63; +#else + unsigned long y = (unsigned long) (x >> 32); + unsigned int m = 31; + if (y == 0) + { + y = (unsigned long) x; + m = 63; + } + _BitScanReverse(&n, y); + return (unsigned int) n ^ m; +#endif +#elif defined(_M_ARM64) + return _CountLeadingZeros64(x); +#endif +} +#else +inline unsigned int nlz (ulonglong x) +{ + static unsigned char table [48] = { + 32, 6, 5, 0, 4, 12, 0, 20, + 15, 3, 11, 0, 0, 18, 25, 31, + 8, 14, 2, 0, 10, 0, 0, 0, + 0, 0, 0, 21, 0, 0, 19, 26, + 7, 0, 13, 0, 16, 1, 22, 27, + 9, 0, 17, 23, 28, 24, 29, 30 + }; + unsigned int y= (unsigned int) (x >> 32); + unsigned int n= 0; + if (y == 0) { + y= (unsigned int) x; + n= 32; + } + y = y | (y >> 1); // Propagate leftmost 1-bit to the right. + y = y | (y >> 2); + y = y | (y >> 4); + y = y | (y >> 8); + y = y & ~(y >> 16); + y = y * 0x3EF5D037; + return n + table[y >> 26]; +} +#endif + +/*********************************************************************//** +Compute the next autoinc value. + +For MySQL replication the autoincrement values can be partitioned among +the nodes. The offset is the start or origin of the autoincrement value +for a particular node. For n nodes the increment will be n and the offset +will be in the interval [1, n]. The formula tries to allocate the next +value for a particular node. + +Note: This function is also called with increment set to the number of +values we want to reserve for multi-value inserts e.g., + + INSERT INTO T VALUES(), (), (); + +innobase_next_autoinc() will be called with increment set to 3 where +autoinc_lock_mode != TRADITIONAL because we want to reserve 3 values for +the multi-value INSERT above. +@return the next value */ +ulonglong +innobase_next_autoinc( +/*==================*/ + ulonglong current, /*!< in: Current value */ + ulonglong need, /*!< in: count of values needed */ + ulonglong step, /*!< in: AUTOINC increment step */ + ulonglong offset, /*!< in: AUTOINC offset */ + ulonglong max_value) /*!< in: max value for type */ +{ + ulonglong next_value; + ulonglong block; + + /* Should never be 0. */ + ut_a(need > 0); + ut_a(step > 0); + ut_a(max_value > 0); + + /* + We need to calculate the "block" value equal to the product + "step * need". However, when calculating this product, an integer + overflow can occur, so we cannot simply use the usual multiplication + operation. The snippet below calculates the product of two numbers + and detects an unsigned integer overflow: + */ + unsigned int m= nlz(need); + unsigned int n= nlz(step); + if (m + n <= 8 * sizeof(ulonglong) - 2) { + // The bit width of the original values is too large, + // therefore we are guaranteed to get an overflow. + goto overflow; + } + block = need * (step >> 1); + if ((longlong) block < 0) { + goto overflow; + } + block += block; + if (step & 1) { + block += need; + if (block < need) { + goto overflow; + } + } + + /* Check for overflow. Current can be > max_value if the value + is in reality a negative value. Also, the visual studio compiler + converts large double values (which hypothetically can then be + passed here as the values of the "current" parameter) automatically + into unsigned long long datatype maximum value: */ + if (current > max_value) { + goto overflow; + } + + /* According to MySQL documentation, if the offset is greater than + the step then the offset is ignored. */ + if (offset > step) { + offset = 0; + } + + /* + Let's round the current value to within a step-size block: + */ + if (current > offset) { + next_value = current - offset; + } else { + next_value = offset - current; + } + next_value -= next_value % step; + + /* + Add an offset to the next value and check that the addition + does not cause an integer overflow: + */ + next_value += offset; + if (next_value < offset) { + goto overflow; + } + + /* + Add a block to the next value and check that the addition + does not cause an integer overflow: + */ + next_value += block; + if (next_value < block) { + goto overflow; + } + + return(next_value); + +overflow: + /* + Allow auto_increment to go over max_value up to max ulonglong. + This allows us to detect that all values are exhausted. + If we don't do this, we will return max_value several times + and get duplicate key errors instead of auto increment value + out of range: + */ + return(~(ulonglong) 0); +} + +/*********************************************************************//** +Initializes some fields in an InnoDB transaction object. */ +static +void +innobase_trx_init( +/*==============*/ + THD* thd, /*!< in: user thread handle */ + trx_t* trx) /*!< in/out: InnoDB transaction handle */ +{ + DBUG_ENTER("innobase_trx_init"); + DBUG_ASSERT(thd == trx->mysql_thd); + + /* Ensure that thd_lock_wait_timeout(), which may be called + while holding lock_sys.latch, by lock_rec_enqueue_waiting(), + will not end up acquiring LOCK_global_system_variables in + intern_sys_var_ptr(). */ + (void) THDVAR(thd, lock_wait_timeout); + + trx->check_foreigns = !thd_test_options( + thd, OPTION_NO_FOREIGN_KEY_CHECKS); + + trx->check_unique_secondary = !thd_test_options( + thd, OPTION_RELAXED_UNIQUE_CHECKS); +#ifdef WITH_WSREP + trx->wsrep = wsrep_on(thd); +#endif + + DBUG_VOID_RETURN; +} + +/*********************************************************************//** +Allocates an InnoDB transaction for a MySQL handler object for DML. +@return InnoDB transaction handle */ +trx_t* +innobase_trx_allocate( +/*==================*/ + THD* thd) /*!< in: user thread handle */ +{ + trx_t* trx; + + DBUG_ENTER("innobase_trx_allocate"); + DBUG_ASSERT(thd != NULL); + DBUG_ASSERT(EQ_CURRENT_THD(thd)); + + trx = trx_create(); + + trx->mysql_thd = thd; + + innobase_trx_init(thd, trx); + + DBUG_RETURN(trx); +} + +/*********************************************************************//** +Gets the InnoDB transaction handle for a MySQL handler object, creates +an InnoDB transaction struct if the corresponding MySQL thread struct still +lacks one. +@return InnoDB transaction handle */ +static inline +trx_t* +check_trx_exists( +/*=============*/ + THD* thd) /*!< in: user thread handle */ +{ + if (trx_t* trx = thd_to_trx(thd)) { + ut_a(trx->magic_n == TRX_MAGIC_N); + innobase_trx_init(thd, trx); + return trx; + } else { + trx = innobase_trx_allocate(thd); + thd_set_ha_data(thd, innodb_hton_ptr, trx); + return trx; + } +} + +/** + Gets current trx. + + This function may be called during InnoDB initialisation, when + innodb_hton_ptr->slot is not yet set to meaningful value. +*/ + +trx_t *current_trx() +{ + THD *thd=current_thd; + if (likely(thd != 0) && innodb_hton_ptr->slot != HA_SLOT_UNDEF) { + return thd_to_trx(thd); + } else { + return(NULL); + } +} + +/*********************************************************************//** +Note that a transaction has been registered with MySQL. +@return true if transaction is registered with MySQL 2PC coordinator */ +static inline +bool +trx_is_registered_for_2pc( +/*======================*/ + const trx_t* trx) /* in: transaction */ +{ + return(trx->is_registered == 1); +} + +/*********************************************************************//** +Note that a transaction has been deregistered. */ +static inline +void +trx_deregister_from_2pc( +/*====================*/ + trx_t* trx) /* in: transaction */ +{ + trx->is_registered= false; + trx->active_commit_ordered= false; +} + +/*********************************************************************//** +Copy table flags from MySQL's HA_CREATE_INFO into an InnoDB table object. +Those flags are stored in .frm file and end up in the MySQL table object, +but are frequently used inside InnoDB so we keep their copies into the +InnoDB table object. */ +static +void +innobase_copy_frm_flags_from_create_info( +/*=====================================*/ + dict_table_t* innodb_table, /*!< in/out: InnoDB table */ + const HA_CREATE_INFO* create_info) /*!< in: create info */ +{ + ibool ps_on; + ibool ps_off; + + if (innodb_table->is_temporary() + || innodb_table->no_rollback()) { + /* Temp tables do not use persistent stats. */ + ps_on = FALSE; + ps_off = TRUE; + } else { + ps_on = create_info->table_options + & HA_OPTION_STATS_PERSISTENT; + ps_off = create_info->table_options + & HA_OPTION_NO_STATS_PERSISTENT; + } + + dict_stats_set_persistent(innodb_table, ps_on, ps_off); + + dict_stats_auto_recalc_set( + innodb_table, + create_info->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON, + create_info->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF); + + innodb_table->stats_sample_pages = create_info->stats_sample_pages; +} + +/*********************************************************************//** +Copy table flags from MySQL's TABLE_SHARE into an InnoDB table object. +Those flags are stored in .frm file and end up in the MySQL table object, +but are frequently used inside InnoDB so we keep their copies into the +InnoDB table object. */ +void +innobase_copy_frm_flags_from_table_share( +/*=====================================*/ + dict_table_t* innodb_table, /*!< in/out: InnoDB table */ + const TABLE_SHARE* table_share) /*!< in: table share */ +{ + ibool ps_on; + ibool ps_off; + + if (innodb_table->is_temporary()) { + /* Temp tables do not use persistent stats */ + ps_on = FALSE; + ps_off = TRUE; + } else { + ps_on = table_share->db_create_options + & HA_OPTION_STATS_PERSISTENT; + ps_off = table_share->db_create_options + & HA_OPTION_NO_STATS_PERSISTENT; + } + + dict_stats_set_persistent(innodb_table, ps_on, ps_off); + + dict_stats_auto_recalc_set( + innodb_table, + table_share->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON, + table_share->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF); + + innodb_table->stats_sample_pages = table_share->stats_sample_pages; +} + +/*********************************************************************//** +Construct ha_innobase handler. */ + +ha_innobase::ha_innobase( +/*=====================*/ + handlerton* hton, + TABLE_SHARE* table_arg) + :handler(hton, table_arg), + m_prebuilt(), + m_user_thd(), + m_int_table_flags(HA_REC_NOT_IN_SEQ + | HA_NULL_IN_KEY + | HA_CAN_VIRTUAL_COLUMNS + | HA_CAN_INDEX_BLOBS + | HA_CAN_SQL_HANDLER + | HA_REQUIRES_KEY_COLUMNS_FOR_DELETE + | HA_PRIMARY_KEY_REQUIRED_FOR_POSITION + | HA_PRIMARY_KEY_IN_READ_INDEX + | HA_BINLOG_ROW_CAPABLE + | HA_CAN_GEOMETRY + | HA_PARTIAL_COLUMN_READ + | HA_TABLE_SCAN_ON_INDEX + | HA_CAN_FULLTEXT + | HA_CAN_FULLTEXT_EXT + /* JAN: TODO: MySQL 5.7 + | HA_CAN_FULLTEXT_HINTS + */ + | HA_CAN_EXPORT + | HA_ONLINE_ANALYZE + | HA_CAN_RTREEKEYS + | HA_CAN_TABLES_WITHOUT_ROLLBACK + | HA_CAN_ONLINE_BACKUPS + | HA_CONCURRENT_OPTIMIZE + | HA_CAN_SKIP_LOCKED + | (srv_force_primary_key ? HA_REQUIRE_PRIMARY_KEY : 0) + ), + m_start_of_scan(), + m_mysql_has_locked() +{} + +/*********************************************************************//** +Destruct ha_innobase handler. */ + +ha_innobase::~ha_innobase() = default; +/*======================*/ + +/*********************************************************************//** +Updates the user_thd field in a handle and also allocates a new InnoDB +transaction handle if needed, and updates the transaction fields in the +m_prebuilt struct. */ +void +ha_innobase::update_thd( +/*====================*/ + THD* thd) /*!< in: thd to use the handle */ +{ + DBUG_ENTER("ha_innobase::update_thd"); + DBUG_PRINT("ha_innobase::update_thd", ("user_thd: %p -> %p", + m_user_thd, thd)); + + /* The table should have been opened in ha_innobase::open(). */ + DBUG_ASSERT(m_prebuilt->table->get_ref_count() > 0); + + trx_t* trx = check_trx_exists(thd); + + ut_ad(!trx->dict_operation_lock_mode); + ut_ad(!trx->dict_operation); + + if (m_prebuilt->trx != trx) { + + row_update_prebuilt_trx(m_prebuilt, trx); + } + + m_user_thd = thd; + + DBUG_ASSERT(m_prebuilt->trx->magic_n == TRX_MAGIC_N); + DBUG_ASSERT(m_prebuilt->trx == thd_to_trx(m_user_thd)); + + DBUG_VOID_RETURN; +} + +/*********************************************************************//** +Updates the user_thd field in a handle and also allocates a new InnoDB +transaction handle if needed, and updates the transaction fields in the +m_prebuilt struct. */ + +void +ha_innobase::update_thd() +/*=====================*/ +{ + THD* thd = ha_thd(); + + ut_ad(EQ_CURRENT_THD(thd)); + update_thd(thd); +} + +/*********************************************************************//** +Registers an InnoDB transaction with the MySQL 2PC coordinator, so that +the MySQL XA code knows to call the InnoDB prepare and commit, or rollback +for the transaction. This MUST be called for every transaction for which +the user may call commit or rollback. Calling this several times to register +the same transaction is allowed, too. This function also registers the +current SQL statement. */ +static inline +void +innobase_register_trx( +/*==================*/ + handlerton* hton, /* in: Innobase handlerton */ + THD* thd, /* in: MySQL thd (connection) object */ + trx_t* trx) /* in: transaction to register */ +{ + ut_ad(!trx->active_commit_ordered); + const trx_id_t trx_id= trx->id; + + trans_register_ha(thd, false, hton, trx_id); + + if (!trx->is_registered) + { + trx->is_registered= true; + if (thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + trans_register_ha(thd, true, hton, trx_id); + } +} + +/* BACKGROUND INFO: HOW THE MYSQL QUERY CACHE WORKS WITH INNODB + ------------------------------------------------------------ + +1) The use of the query cache for TBL is disabled when there is an +uncommitted change to TBL. + +2) When a change to TBL commits, InnoDB stores the current value of +its global trx id counter, let us denote it by INV_TRX_ID, to the table object +in the InnoDB data dictionary, and does only allow such transactions whose +id <= INV_TRX_ID to use the query cache. + +3) When InnoDB does an INSERT/DELETE/UPDATE to a table TBL, or an implicit +modification because an ON DELETE CASCADE, we invalidate the MySQL query cache +of TBL immediately. + +How this is implemented inside InnoDB: + +1) Since every modification always sets an IX type table lock on the InnoDB +table, it is easy to check if there can be uncommitted modifications for a +table: just check if there are locks in the lock list of the table. + +2) When a transaction inside InnoDB commits, it reads the global trx id +counter and stores the value INV_TRX_ID to the tables on which it had a lock. + +3) If there is an implicit table change from ON DELETE CASCADE or SET NULL, +InnoDB calls an invalidate method for the MySQL query cache for that table. + +How this is implemented inside sql_cache.cc: + +1) The query cache for an InnoDB table TBL is invalidated immediately at an +INSERT/UPDATE/DELETE, just like in the case of MyISAM. No need to delay +invalidation to the transaction commit. + +2) To store or retrieve a value from the query cache of an InnoDB table TBL, +any query must first ask InnoDB's permission. We must pass the thd as a +parameter because InnoDB will look at the trx id, if any, associated with +that thd. Also the full_name which is used as key to search for the table +object. The full_name is a string containing the normalized path to the +table in the canonical format. + +3) Use of the query cache for InnoDB tables is now allowed also when +AUTOCOMMIT==0 or we are inside BEGIN ... COMMIT. Thus transactions no longer +put restrictions on the use of the query cache. +*/ + +/** Check if mysql can allow the transaction to read from/store to +the query cache. +@param[in] table table object +@param[in] trx transaction object +@return whether the storing or retrieving from the query cache is permitted */ +TRANSACTIONAL_TARGET +static bool innobase_query_caching_table_check_low( + dict_table_t* table, trx_t* trx) +{ + /* The following conditions will decide the query cache + retrieval or storing into: + + (1) There should not be any locks on the table. + (2) Someother trx shouldn't invalidate the cache before this + transaction started. + (3) Read view shouldn't exist. If exists then the view + low_limit_id should be greater than or equal to the transaction that + invalidates the cache for the particular table. + + For read-only transaction: should satisfy (1) and (3) + For read-write transaction: should satisfy (1), (2), (3) */ + + const trx_id_t inv = table->query_cache_inv_trx_id; + + if (trx->id && trx->id < inv) { + return false; + } + + if (trx->read_view.is_open() && trx->read_view.low_limit_id() < inv) { + return false; + } + +#if !defined NO_ELISION && !defined SUX_LOCK_GENERIC + if (xbegin()) { + if (table->lock_mutex_is_locked()) + xabort(); + auto len = UT_LIST_GET_LEN(table->locks); + xend(); + return len == 0; + } +#endif + + table->lock_mutex_lock(); + auto len= UT_LIST_GET_LEN(table->locks); + table->lock_mutex_unlock(); + return len == 0; +} + +/** Checks if MySQL at the moment is allowed for this table to retrieve a +consistent read result, or store it to the query cache. +@param[in,out] trx transaction +@param[in] norm_name concatenation of database name, + '/' char, table name +@return whether storing or retrieving from the query cache is permitted */ +static bool innobase_query_caching_table_check( + trx_t* trx, + const char* norm_name) +{ + dict_table_t* table = dict_table_open_on_name( + norm_name, false, DICT_ERR_IGNORE_FK_NOKEY); + + if (table == NULL) { + return false; + } + + /* Start the transaction if it is not started yet */ + trx_start_if_not_started(trx, false); + + bool allow = innobase_query_caching_table_check_low(table, trx); + + dict_table_close(table); + + if (allow) { + /* If the isolation level is high, assign a read view for the + transaction if it does not yet have one */ + + if (trx->isolation_level >= TRX_ISO_REPEATABLE_READ + && !srv_read_only_mode + && !trx->read_view.is_open()) { + + /* Start the transaction if it is not started yet */ + trx_start_if_not_started(trx, false); + + trx->read_view.open(trx); + } + } + + return allow; +} + +/******************************************************************//** +The MySQL query cache uses this to check from InnoDB if the query cache at +the moment is allowed to operate on an InnoDB table. The SQL query must +be a non-locking SELECT. + +The query cache is allowed to operate on certain query only if this function +returns TRUE for all tables in the query. + +If thd is not in the autocommit state, this function also starts a new +transaction for thd if there is no active trx yet, and assigns a consistent +read view to it if there is no read view yet. + +Why a deadlock of threads is not possible: the query cache calls this function +at the start of a SELECT processing. Then the calling thread cannot be +holding any InnoDB semaphores. The calling thread is holding the +query cache mutex, and this function will reserve the trx_sys.mutex. +@return TRUE if permitted, FALSE if not; note that the value FALSE +does not mean we should invalidate the query cache: invalidation is +called explicitly */ +static +my_bool +innobase_query_caching_of_table_permitted( +/*======================================*/ + THD* thd, /*!< in: thd of the user who is trying to + store a result to the query cache or + retrieve it */ + const char* full_name, /*!< in: normalized path to the table */ + uint full_name_len, /*!< in: length of the normalized path + to the table */ + ulonglong *) +{ + char norm_name[1000]; + trx_t* trx = check_trx_exists(thd); + + ut_a(full_name_len < 999); + + if (trx->isolation_level == TRX_ISO_SERIALIZABLE) { + /* In the SERIALIZABLE mode we add LOCK IN SHARE MODE to every + plain SELECT if AUTOCOMMIT is not on. */ + + return(false); + } + + if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) + && trx->n_mysql_tables_in_use == 0) { + /* We are going to retrieve the query result from the query + cache. This cannot be a store operation to the query cache + because then MySQL would have locks on tables already. + + TODO: if the user has used LOCK TABLES to lock the table, + then we open a transaction in the call of row_.. below. + That trx can stay open until UNLOCK TABLES. The same problem + exists even if we do not use the query cache. MySQL should be + modified so that it ALWAYS calls some cleanup function when + the processing of a query ends! + + We can imagine we instantaneously serialize this consistent + read trx to the current trx id counter. If trx2 would have + changed the tables of a query result stored in the cache, and + trx2 would have already committed, making the result obsolete, + then trx2 would have already invalidated the cache. Thus we + can trust the result in the cache is ok for this query. */ + + return(true); + } + + /* Normalize the table name to InnoDB format */ + normalize_table_name(norm_name, full_name); + + innobase_register_trx(innodb_hton_ptr, thd, trx); + + return innobase_query_caching_table_check(trx, norm_name); +} + +/*****************************************************************//** +Invalidates the MySQL query cache for the table. */ +void +innobase_invalidate_query_cache( +/*============================*/ + trx_t* trx, /*!< in: transaction which + modifies the table */ + const char* full_name) /*!< in: concatenation of + database name, path separator, + table name, null char NUL; + NOTE that in Windows this is + always in LOWER CASE! */ +{ + /* Note that the query cache mutex is just above the trx_sys.mutex. + The caller of this function must not have latches of a lower rank. */ + +#ifdef HAVE_QUERY_CACHE + char qcache_key_name[2 * (NAME_LEN + 1)]; + char db_name[NAME_CHAR_LEN * MY_CS_MBMAXLEN + 1]; + const char *key_ptr; + size_t tabname_len; + + // Extract the database name. + key_ptr= strchr(full_name, '/'); + DBUG_ASSERT(key_ptr != NULL); // Database name should be present + size_t dbname_len= size_t(key_ptr - full_name); + memcpy(db_name, full_name, dbname_len); + db_name[dbname_len]= '\0'; + + /* Construct the key("db-name\0table$name\0") for the query cache using + the path name("db@002dname\0table@0024name\0") of the table in its + canonical form. */ + dbname_len = filename_to_tablename(db_name, qcache_key_name, + sizeof(qcache_key_name)); + tabname_len = filename_to_tablename(++key_ptr, + (qcache_key_name + dbname_len + 1), + sizeof(qcache_key_name) - + dbname_len - 1); + + /* Argument TRUE below means we are using transactions */ + mysql_query_cache_invalidate4(trx->mysql_thd, + qcache_key_name, + uint(dbname_len + tabname_len + 2), + TRUE); +#endif +} + +/** Quote a standard SQL identifier like index or column name. +@param[in] file output stream +@param[in] trx InnoDB transaction, or NULL +@param[in] id identifier to quote */ +void +innobase_quote_identifier( + FILE* file, + trx_t* trx, + const char* id) +{ + const int q = trx != NULL && trx->mysql_thd != NULL + ? get_quote_char_for_identifier(trx->mysql_thd, id, strlen(id)) + : '`'; + + if (q == EOF) { + fputs(id, file); + } else { + putc(q, file); + + while (int c = *id++) { + if (c == q) { + putc(c, file); + } + putc(c, file); + } + + putc(q, file); + } +} + +/** Quote a standard SQL identifier like tablespace, index or column name. +@param[in] trx InnoDB transaction, or NULL +@param[in] id identifier to quote +@return quoted identifier */ +std::string +innobase_quote_identifier( +/*======================*/ + trx_t* trx, + const char* id) +{ + std::string quoted_identifier; + const int q = trx != NULL && trx->mysql_thd != NULL + ? get_quote_char_for_identifier(trx->mysql_thd, id, strlen(id)) + : '`'; + + if (q == EOF) { + quoted_identifier.append(id); + } else { + quoted_identifier += char(q); + quoted_identifier.append(id); + quoted_identifier += char(q); + } + + return (quoted_identifier); +} + +/** Convert a table name to the MySQL system_charset_info (UTF-8) +and quote it. +@param[out] buf buffer for converted identifier +@param[in] buflen length of buf, in bytes +@param[in] id identifier to convert +@param[in] idlen length of id, in bytes +@param[in] thd MySQL connection thread, or NULL +@return pointer to the end of buf */ +static +char* +innobase_convert_identifier( + char* buf, + ulint buflen, + const char* id, + ulint idlen, + THD* thd) +{ + const char* s = id; + + char nz[MAX_TABLE_NAME_LEN + 1]; + char nz2[MAX_TABLE_NAME_LEN + 1]; + + /* Decode the table name. The MySQL function expects + a NUL-terminated string. The input and output strings + buffers must not be shared. */ + ut_a(idlen <= MAX_TABLE_NAME_LEN); + memcpy(nz, id, idlen); + nz[idlen] = 0; + + s = nz2; + idlen = explain_filename(thd, nz, nz2, sizeof nz2, + EXPLAIN_PARTITIONS_AS_COMMENT); + if (idlen > buflen) { + idlen = buflen; + } + memcpy(buf, s, idlen); + return(buf + idlen); +} + +/*****************************************************************//** +Convert a table name to the MySQL system_charset_info (UTF-8). +@return pointer to the end of buf */ +char* +innobase_convert_name( +/*==================*/ + char* buf, /*!< out: buffer for converted identifier */ + ulint buflen, /*!< in: length of buf, in bytes */ + const char* id, /*!< in: table name to convert */ + ulint idlen, /*!< in: length of id, in bytes */ + THD* thd) /*!< in: MySQL connection thread, or NULL */ +{ + char* s = buf; + const char* bufend = buf + buflen; + + const char* slash = (const char*) memchr(id, '/', idlen); + + if (slash == NULL) { + return(innobase_convert_identifier( + buf, buflen, id, idlen, thd)); + } + + /* Print the database name and table name separately. */ + s = innobase_convert_identifier(s, ulint(bufend - s), + id, ulint(slash - id), thd); + if (s < bufend) { + *s++ = '.'; + s = innobase_convert_identifier(s, ulint(bufend - s), + slash + 1, idlen + - ulint(slash - id) - 1, + thd); + } + + return(s); +} + +/*****************************************************************//** +A wrapper function of innobase_convert_name(), convert a table name +to the MySQL system_charset_info (UTF-8) and quote it if needed. +@return pointer to the end of buf */ +void +innobase_format_name( +/*==================*/ + char* buf, /*!< out: buffer for converted identifier */ + ulint buflen, /*!< in: length of buf, in bytes */ + const char* name) /*!< in: table name to format */ +{ + const char* bufend; + + bufend = innobase_convert_name(buf, buflen, name, strlen(name), NULL); + + ut_ad((ulint) (bufend - buf) < buflen); + + buf[bufend - buf] = '\0'; +} + +/**********************************************************************//** +Determines if the currently running transaction has been interrupted. +@return true if interrupted */ +bool +trx_is_interrupted( +/*===============*/ + const trx_t* trx) /*!< in: transaction */ +{ + return(trx && trx->mysql_thd && thd_kill_level(trx->mysql_thd)); +} + +/**************************************************************//** +Resets some fields of a m_prebuilt struct. The template is used in fast +retrieval of just those column values MySQL needs in its processing. */ +void +ha_innobase::reset_template(void) +/*=============================*/ +{ + ut_ad(m_prebuilt->magic_n == ROW_PREBUILT_ALLOCATED); + ut_ad(m_prebuilt->magic_n2 == m_prebuilt->magic_n); + + /* Force table to be freed in close_thread_table(). */ + DBUG_EXECUTE_IF("free_table_in_fts_query", + if (m_prebuilt->in_fts_query) { + table->mark_table_for_reopen(); + } + ); + + m_prebuilt->keep_other_fields_on_keyread = false; + m_prebuilt->read_just_key = 0; + m_prebuilt->in_fts_query = 0; + + /* Reset index condition pushdown state. */ + if (m_prebuilt->idx_cond) { + m_prebuilt->idx_cond = NULL; + m_prebuilt->idx_cond_n_cols = 0; + /* Invalidate m_prebuilt->mysql_template + in ha_innobase::write_row(). */ + m_prebuilt->template_type = ROW_MYSQL_NO_TEMPLATE; + } + if (m_prebuilt->pk_filter) { + m_prebuilt->pk_filter = NULL; + m_prebuilt->template_type = ROW_MYSQL_NO_TEMPLATE; + } +} + +/*****************************************************************//** +Call this when you have opened a new table handle in HANDLER, before you +call index_read_map() etc. Actually, we can let the cursor stay open even +over a transaction commit! Then you should call this before every operation, +fetch next etc. This function inits the necessary things even after a +transaction commit. */ + +void +ha_innobase::init_table_handle_for_HANDLER(void) +/*============================================*/ +{ + /* If current thd does not yet have a trx struct, create one. + If the current handle does not yet have a m_prebuilt struct, create + one. Update the trx pointers in the m_prebuilt struct. Normally + this operation is done in external_lock. */ + + update_thd(ha_thd()); + + /* Initialize the m_prebuilt struct much like it would be inited in + external_lock */ + + /* If the transaction is not started yet, start it */ + + trx_start_if_not_started_xa(m_prebuilt->trx, false); + + /* Assign a read view if the transaction does not have it yet */ + + m_prebuilt->trx->read_view.open(m_prebuilt->trx); + + innobase_register_trx(ht, m_user_thd, m_prebuilt->trx); + + /* We did the necessary inits in this function, no need to repeat them + in row_search_mvcc() */ + + m_prebuilt->sql_stat_start = FALSE; + + /* We let HANDLER always to do the reads as consistent reads, even + if the trx isolation level would have been specified as SERIALIZABLE */ + + m_prebuilt->select_lock_type = LOCK_NONE; + m_prebuilt->stored_select_lock_type = LOCK_NONE; + + /* Always fetch all columns in the index record */ + + m_prebuilt->hint_need_to_fetch_extra_cols = ROW_RETRIEVE_ALL_COLS; + + /* We want always to fetch all columns in the whole row? Or do + we???? */ + + m_prebuilt->used_in_HANDLER = TRUE; + + reset_template(); + m_prebuilt->trx->bulk_insert = false; +} + +/*********************************************************************//** +Free any resources that were allocated and return failure. +@return always return 1 */ +static int innodb_init_abort() +{ + DBUG_ENTER("innodb_init_abort"); + + if (fil_system.temp_space) { + fil_system.temp_space->close(); + } + + srv_sys_space.shutdown(); + if (srv_tmp_space.get_sanity_check_status()) { + srv_tmp_space.delete_files(); + } + srv_tmp_space.shutdown(); + + DBUG_RETURN(1); +} + +/** Return the minimum buffer pool size based on page size */ +static inline ulint min_buffer_pool_size() +{ + ulint s= (BUF_LRU_MIN_LEN + BUF_LRU_MIN_LEN / 4) * srv_page_size; + /* buf_pool_chunk_size minimum is 1M, so round up to a multiple */ + ulint alignment= 1U << 20; + return UT_CALC_ALIGN(s, alignment); +} + +/** Validate the requested buffer pool size. Also, reserve the necessary +memory needed for buffer pool resize. +@param[in] thd thread handle +@param[in] var pointer to system variable +@param[out] save immediate result for update function +@param[in] value incoming string +@return 0 on success, 1 on failure. +*/ +static +int +innodb_buffer_pool_size_validate( + THD* thd, + struct st_mysql_sys_var* var, + void* save, + struct st_mysql_value* value); + +/** Update the system variable innodb_buffer_pool_size using the "saved" +value. This function is registered as a callback with MySQL. +@param[in] thd thread handle +@param[in] var pointer to system variable +@param[out] var_ptr where the formal string goes +@param[in] save immediate result from check function */ +static +void +innodb_buffer_pool_size_update( + THD* thd, + struct st_mysql_sys_var* var, + void* var_ptr, + const void* save); + +static MYSQL_SYSVAR_ULONGLONG(buffer_pool_size, innobase_buffer_pool_size, + PLUGIN_VAR_RQCMDARG, + "The size of the memory buffer InnoDB uses to cache data and indexes of its tables.", + innodb_buffer_pool_size_validate, + innodb_buffer_pool_size_update, + 128ULL << 20, + 2ULL << 20, + LLONG_MAX, 1024*1024L); + +/****************************************************************//** +Gives the file extension of an InnoDB single-table tablespace. */ +static const char* ha_innobase_exts[] = { + dot_ext[IBD], + dot_ext[ISL], + NullS +}; + +/** Determine if system-versioned data was modified by the transaction. +@param[in,out] thd current session +@param[out] trx_id transaction start ID +@return transaction commit ID +@retval 0 if no system-versioned data was affected by the transaction */ +static ulonglong innodb_prepare_commit_versioned(THD* thd, ulonglong *trx_id) +{ + if (trx_t *trx= thd_to_trx(thd)) + { + *trx_id= trx->id; + bool versioned= false; + + for (auto &t : trx->mod_tables) + { + if (t.second.is_versioned()) + { + DBUG_ASSERT(t.first->versioned_by_id()); + DBUG_ASSERT(trx->rsegs.m_redo.rseg); + versioned= true; + if (!trx->bulk_insert) + break; + } + if (t.second.is_bulk_insert()) + { + ut_ad(trx->bulk_insert); + if (t.second.write_bulk(t.first, trx)) + return ULONGLONG_MAX; + } + } + + return versioned ? trx_sys.get_new_trx_id() : 0; + } + + *trx_id= 0; + return 0; +} + +/** Initialize and normalize innodb_buffer_pool_{chunk_,}size. */ +static void innodb_buffer_pool_size_init() +{ + if (srv_buf_pool_chunk_unit > srv_buf_pool_size) + { + /* Size unit of buffer pool is larger than srv_buf_pool_size. + adjust srv_buf_pool_chunk_unit for srv_buf_pool_size. */ + srv_buf_pool_chunk_unit = srv_buf_pool_size; + } + else if (srv_buf_pool_chunk_unit == 0) + { + srv_buf_pool_chunk_unit = srv_buf_pool_size / 64; + my_large_page_truncate(&srv_buf_pool_chunk_unit); + } + + if (srv_buf_pool_chunk_unit < buf_pool_chunk_min_size) + srv_buf_pool_chunk_unit = buf_pool_chunk_min_size; + + srv_buf_pool_size = buf_pool_size_align(srv_buf_pool_size); + innobase_buffer_pool_size = srv_buf_pool_size; +} + + +static bool +compression_algorithm_is_not_loaded(ulong compression_algorithm, myf flags) +{ + bool is_loaded[PAGE_ALGORITHM_LAST+1]= { 1, 1, provider_service_lz4->is_loaded, + provider_service_lzo->is_loaded, provider_service_lzma->is_loaded, + provider_service_bzip2->is_loaded, provider_service_snappy->is_loaded }; + + DBUG_ASSERT(compression_algorithm <= PAGE_ALGORITHM_LAST); + + if (is_loaded[compression_algorithm]) + return 0; + + my_printf_error(HA_ERR_UNSUPPORTED, "InnoDB: compression algorithm %s (%u)" + " is not available. Please, load the corresponding provider plugin.", flags, + page_compression_algorithms[compression_algorithm], compression_algorithm); + return 1; +} + +/** Initialize, validate and normalize the InnoDB startup parameters. +@return failure code +@retval 0 on success +@retval HA_ERR_OUT_OF_MEM when out of memory +@retval HA_ERR_INITIALIZATION when some parameters are out of range */ +static int innodb_init_params() +{ + DBUG_ENTER("innodb_init_params"); + + ulong num_pll_degree; + + /* Check that values don't overflow on 32-bit systems. */ + if (sizeof(ulint) == 4) { + if (innobase_buffer_pool_size > UINT_MAX32) { + sql_print_error( + "innodb_buffer_pool_size can't be over 4GB" + " on 32-bit systems"); + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + } + } + + /* The buffer pool needs to be able to accommodate enough many + pages, even for larger pages */ + MYSQL_SYSVAR_NAME(buffer_pool_size).min_val= min_buffer_pool_size(); + + if (innobase_buffer_pool_size < MYSQL_SYSVAR_NAME(buffer_pool_size).min_val) { + ib::error() << "innodb_page_size=" + << srv_page_size << " requires " + << "innodb_buffer_pool_size >= " + << (MYSQL_SYSVAR_NAME(buffer_pool_size).min_val >> 20) + << "MiB current " << (innobase_buffer_pool_size >> 20) + << "MiB"; + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + + if (compression_algorithm_is_not_loaded(innodb_compression_algorithm, ME_ERROR_LOG)) + DBUG_RETURN(HA_ERR_INITIALIZATION); + + if ((srv_encrypt_tables || srv_encrypt_log + || innodb_encrypt_temporary_tables) + && !encryption_key_id_exists(FIL_DEFAULT_ENCRYPTION_KEY)) { + sql_print_error("InnoDB: cannot enable encryption, " + "encryption plugin is not available"); + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + +#ifdef _WIN32 + if (!is_filename_allowed(srv_buf_dump_filename, + strlen(srv_buf_dump_filename), FALSE)) { + sql_print_error("InnoDB: innodb_buffer_pool_filename" + " cannot have colon (:) in the file name."); + DBUG_RETURN(HA_ERR_INITIALIZATION); + } +#endif + + /* First calculate the default path for innodb_data_home_dir etc., + in case the user has not given any value. + + Note that when using the embedded server, the datadirectory is not + necessarily the current directory of this program. */ + + fil_path_to_mysql_datadir = +#ifndef HAVE_REPLICATION + mysqld_embedded ? mysql_real_data_home : +#endif + "./"; + + /* Set InnoDB initialization parameters according to the values + read from MySQL .cnf file */ + + /* The default dir for data files is the datadir of MySQL */ + + srv_data_home = innobase_data_home_dir + ? innobase_data_home_dir + : const_cast<char*>(fil_path_to_mysql_datadir); +#ifdef WITH_WSREP + /* If we use the wsrep API, then we need to tell the server + the path to the data files (for passing it to the SST scripts): */ + wsrep_set_data_home_dir(srv_data_home); +#endif /* WITH_WSREP */ + + + /*--------------- Shared tablespaces -------------------------*/ + + /* Check that the value of system variable innodb_page_size was + set correctly. Its value was put into srv_page_size. If valid, + return the associated srv_page_size_shift. */ + srv_page_size_shift = innodb_page_size_validate(srv_page_size); + if (!srv_page_size_shift) { + sql_print_error("InnoDB: Invalid page size=%lu.\n", + srv_page_size); + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + + srv_sys_space.set_space_id(TRX_SYS_SPACE); + + switch (srv_checksum_algorithm) { + case SRV_CHECKSUM_ALGORITHM_FULL_CRC32: + case SRV_CHECKSUM_ALGORITHM_STRICT_FULL_CRC32: + srv_sys_space.set_flags(FSP_FLAGS_FCRC32_MASK_MARKER + | FSP_FLAGS_FCRC32_PAGE_SSIZE()); + break; + default: + srv_sys_space.set_flags(FSP_FLAGS_PAGE_SSIZE()); + } + + srv_sys_space.set_path(srv_data_home); + + /* Supports raw devices */ + if (!srv_sys_space.parse_params(innobase_data_file_path, true)) { + ib::error() << "Unable to parse innodb_data_file_path=" + << innobase_data_file_path; + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + + srv_tmp_space.set_path(srv_data_home); + + /* Temporary tablespace is in full crc32 format. */ + srv_tmp_space.set_flags(FSP_FLAGS_FCRC32_MASK_MARKER + | FSP_FLAGS_FCRC32_PAGE_SSIZE()); + + if (!srv_tmp_space.parse_params(innobase_temp_data_file_path, false)) { + ib::error() << "Unable to parse innodb_temp_data_file_path=" + << innobase_temp_data_file_path; + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + + /* Perform all sanity check before we take action of deleting files*/ + if (srv_sys_space.intersection(&srv_tmp_space)) { + sql_print_error("innodb_temporary and innodb_system" + " file names seem to be the same."); + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + + srv_sys_space.normalize_size(); + srv_tmp_space.normalize_size(); + + /* ------------ UNDO tablespaces files ---------------------*/ + if (!srv_undo_dir) { + srv_undo_dir = const_cast<char*>(fil_path_to_mysql_datadir); + } + + if (strchr(srv_undo_dir, ';')) { + sql_print_error("syntax error in innodb_undo_directory"); + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + + /* -------------- All log files ---------------------------*/ + + /* The default dir for log files is the datadir of MySQL */ + + if (!srv_log_group_home_dir) { + srv_log_group_home_dir + = const_cast<char*>(fil_path_to_mysql_datadir); + } + + if (strchr(srv_log_group_home_dir, ';')) { + sql_print_error("syntax error in innodb_log_group_home_dir"); + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + + DBUG_ASSERT(innodb_change_buffering <= IBUF_USE_ALL); + + /* Check that interdependent parameters have sane values. */ + if (srv_max_buf_pool_modified_pct < srv_max_dirty_pages_pct_lwm) { + sql_print_warning("InnoDB: innodb_max_dirty_pages_pct_lwm" + " cannot be set higher than" + " innodb_max_dirty_pages_pct.\n" + "InnoDB: Setting" + " innodb_max_dirty_pages_pct_lwm to %lf\n", + srv_max_buf_pool_modified_pct); + + srv_max_dirty_pages_pct_lwm = srv_max_buf_pool_modified_pct; + } + + if (srv_max_io_capacity == SRV_MAX_IO_CAPACITY_DUMMY_DEFAULT) { + + if (srv_io_capacity >= SRV_MAX_IO_CAPACITY_LIMIT / 2) { + /* Avoid overflow. */ + srv_max_io_capacity = SRV_MAX_IO_CAPACITY_LIMIT; + } else { + /* The user has not set the value. We should + set it based on innodb_io_capacity. */ + srv_max_io_capacity = + ut_max(2 * srv_io_capacity, 2000UL); + } + + } else if (srv_max_io_capacity < srv_io_capacity) { + sql_print_warning("InnoDB: innodb_io_capacity" + " cannot be set higher than" + " innodb_io_capacity_max." + "Setting innodb_io_capacity=%lu", + srv_max_io_capacity); + + srv_io_capacity = srv_max_io_capacity; + } + + if (UNIV_PAGE_SIZE_DEF != srv_page_size) { + ib::info() << "innodb_page_size=" << srv_page_size; + + srv_max_undo_log_size = std::max( + srv_max_undo_log_size, + ulonglong(SRV_UNDO_TABLESPACE_SIZE_IN_PAGES) + << srv_page_size_shift); + } + + srv_buf_pool_size = ulint(innobase_buffer_pool_size); + + if (innobase_open_files < 10) { + innobase_open_files = 300; + if (srv_file_per_table && tc_size > 300 && tc_size < open_files_limit) { + innobase_open_files = tc_size; + } + } + + if (innobase_open_files > open_files_limit) { + ib::warn() << "innodb_open_files " << innobase_open_files + << " should not be greater" + << " than the open_files_limit " << open_files_limit; + if (innobase_open_files > tc_size) { + innobase_open_files = tc_size; + } + } + + srv_max_n_open_files = innobase_open_files; + srv_innodb_status = (ibool) innobase_create_status_file; + + srv_print_verbose_log = mysqld_embedded ? 0 : 1; + + /* Round up fts_sort_pll_degree to nearest power of 2 number */ + for (num_pll_degree = 1; + num_pll_degree < fts_sort_pll_degree; + num_pll_degree <<= 1) { + + /* No op */ + } + + fts_sort_pll_degree = num_pll_degree; + + /* Store the default charset-collation number of this MySQL + installation */ + + data_mysql_default_charset_coll = (ulint) default_charset_info->number; + +#ifndef _WIN32 + if (srv_use_atomic_writes && my_may_have_atomic_write) { + /* + Force O_DIRECT on Unixes (on Windows writes are always + unbuffered) + */ + switch (srv_file_flush_method) { + case SRV_O_DIRECT: + case SRV_O_DIRECT_NO_FSYNC: + break; + default: + srv_file_flush_method = SRV_O_DIRECT; + fprintf(stderr, "InnoDB: using O_DIRECT due to atomic writes.\n"); + } + } +#endif + +#if defined __linux__ || defined _WIN32 + if (srv_flush_log_at_trx_commit == 2) { + /* Do not disable the file system cache if + innodb_flush_log_at_trx_commit=2. */ + log_sys.log_buffered = true; + } +#endif + + if (srv_read_only_mode) { + ib::info() << "Started in read only mode"; + srv_use_doublewrite_buf = FALSE; + } + +#if !defined LINUX_NATIVE_AIO && !defined HAVE_URING && !defined _WIN32 + /* Currently native AIO is supported only on windows and linux + and that also when the support is compiled in. In all other + cases, we ignore the setting of innodb_use_native_aio. */ + srv_use_native_aio = FALSE; +#endif +#ifdef HAVE_URING + if (srv_use_native_aio && io_uring_may_be_unsafe) { + sql_print_warning("innodb_use_native_aio may cause " + "hangs with this kernel %s; see " + "https://jira.mariadb.org/browse/MDEV-26674", + io_uring_may_be_unsafe); + } +#endif + +#ifndef _WIN32 + ut_ad(srv_file_flush_method <= SRV_O_DIRECT_NO_FSYNC); +#else + switch (srv_file_flush_method) { + case SRV_ALL_O_DIRECT_FSYNC + 1 /* "async_unbuffered"="unbuffered" */: + srv_file_flush_method = SRV_ALL_O_DIRECT_FSYNC; + break; + case SRV_ALL_O_DIRECT_FSYNC + 2 /* "normal"="fsync" */: + srv_file_flush_method = SRV_FSYNC; + break; + default: + ut_ad(srv_file_flush_method <= SRV_ALL_O_DIRECT_FSYNC); + } +#endif + innodb_buffer_pool_size_init(); + + srv_lock_table_size = 5 * (srv_buf_pool_size >> srv_page_size_shift); + DBUG_RETURN(0); +} + +/** Initialize the InnoDB storage engine plugin. +@param[in,out] p InnoDB handlerton +@return error code +@retval 0 on success */ +static int innodb_init(void* p) +{ + DBUG_ENTER("innodb_init"); + handlerton* innobase_hton= static_cast<handlerton*>(p); + innodb_hton_ptr = innobase_hton; + + innobase_hton->db_type = DB_TYPE_INNODB; + innobase_hton->savepoint_offset = sizeof(trx_named_savept_t); + innobase_hton->close_connection = innobase_close_connection; + innobase_hton->kill_query = innobase_kill_query; + innobase_hton->savepoint_set = innobase_savepoint; + innobase_hton->savepoint_rollback = innobase_rollback_to_savepoint; + + innobase_hton->savepoint_rollback_can_release_mdl = + innobase_rollback_to_savepoint_can_release_mdl; + + innobase_hton->savepoint_release = innobase_release_savepoint; + innobase_hton->prepare_ordered= NULL; + innobase_hton->commit_ordered= innobase_commit_ordered; + innobase_hton->commit = innobase_commit; + innobase_hton->rollback = innobase_rollback; + innobase_hton->prepare = innobase_xa_prepare; + innobase_hton->recover = innobase_xa_recover; + innobase_hton->commit_by_xid = innobase_commit_by_xid; + innobase_hton->rollback_by_xid = innobase_rollback_by_xid; + innobase_hton->commit_checkpoint_request = innodb_log_flush_request; + innobase_hton->create = innobase_create_handler; + + innobase_hton->drop_database = innodb_drop_database; + innobase_hton->panic = innobase_end; + innobase_hton->pre_shutdown = innodb_preshutdown; + + innobase_hton->start_consistent_snapshot = + innobase_start_trx_and_assign_read_view; + + innobase_hton->flush_logs = innobase_flush_logs; + innobase_hton->show_status = innobase_show_status; + innobase_hton->notify_tabledef_changed= innodb_notify_tabledef_changed; + innobase_hton->flags = + HTON_SUPPORTS_EXTENDED_KEYS | HTON_SUPPORTS_FOREIGN_KEYS | + HTON_NATIVE_SYS_VERSIONING | + HTON_WSREP_REPLICATION | + HTON_REQUIRES_CLOSE_AFTER_TRUNCATE | + HTON_TRUNCATE_REQUIRES_EXCLUSIVE_USE | + HTON_REQUIRES_NOTIFY_TABLEDEF_CHANGED_AFTER_COMMIT; + +#ifdef WITH_WSREP + innobase_hton->abort_transaction=wsrep_abort_transaction; + innobase_hton->set_checkpoint=innobase_wsrep_set_checkpoint; + innobase_hton->get_checkpoint=innobase_wsrep_get_checkpoint; + innobase_hton->disable_internal_writes=innodb_disable_internal_writes; +#endif /* WITH_WSREP */ + + innobase_hton->check_version = innodb_check_version; + innobase_hton->signal_ddl_recovery_done = innodb_ddl_recovery_done; + + innobase_hton->tablefile_extensions = ha_innobase_exts; + innobase_hton->table_options = innodb_table_option_list; + + /* System Versioning */ + innobase_hton->prepare_commit_versioned + = innodb_prepare_commit_versioned; + + innodb_remember_check_sysvar_funcs(); + + compile_time_assert(DATA_MYSQL_TRUE_VARCHAR == MYSQL_TYPE_VARCHAR); + +#ifndef DBUG_OFF + static const char test_filename[] = "-@"; + char test_tablename[sizeof test_filename + + sizeof(srv_mysql50_table_name_prefix) - 1]; + DBUG_ASSERT(sizeof test_tablename - 1 + == filename_to_tablename(test_filename, + test_tablename, + sizeof test_tablename, true)); + DBUG_ASSERT(!strncmp(test_tablename, + srv_mysql50_table_name_prefix, + sizeof srv_mysql50_table_name_prefix - 1)); + DBUG_ASSERT(!strcmp(test_tablename + + sizeof srv_mysql50_table_name_prefix - 1, + test_filename)); +#endif /* DBUG_OFF */ + + os_file_set_umask(my_umask); + + /* Setup the memory alloc/free tracing mechanisms before calling + any functions that could possibly allocate memory. */ + ut_new_boot(); + + if (int error = innodb_init_params()) { + DBUG_RETURN(error); + } + + /* After this point, error handling has to use + innodb_init_abort(). */ + +#ifdef HAVE_PSI_INTERFACE + /* Register keys with MySQL performance schema */ + int count; + +# ifdef UNIV_PFS_MUTEX + count = array_elements(all_innodb_mutexes); + mysql_mutex_register("innodb", all_innodb_mutexes, count); +# endif /* UNIV_PFS_MUTEX */ + +# ifdef UNIV_PFS_RWLOCK + count = array_elements(all_innodb_rwlocks); + mysql_rwlock_register("innodb", all_innodb_rwlocks, count); +# endif /* UNIV_PFS_MUTEX */ + +# ifdef UNIV_PFS_THREAD + count = array_elements(all_innodb_threads); + mysql_thread_register("innodb", all_innodb_threads, count); +# endif /* UNIV_PFS_THREAD */ + +# ifdef UNIV_PFS_IO + count = array_elements(all_innodb_files); + mysql_file_register("innodb", all_innodb_files, count); +# endif /* UNIV_PFS_IO */ +#endif /* HAVE_PSI_INTERFACE */ + + bool create_new_db = false; + + /* Check whether the data files exist. */ + dberr_t err = srv_sys_space.check_file_spec(&create_new_db, 5U << 20); + + if (err != DB_SUCCESS) { + DBUG_RETURN(innodb_init_abort()); + } + + err = srv_start(create_new_db); + + if (err != DB_SUCCESS) { + innodb_shutdown(); + DBUG_RETURN(innodb_init_abort()); + } + + srv_was_started = true; + innodb_params_adjust(); + + innobase_old_blocks_pct = buf_LRU_old_ratio_update( + innobase_old_blocks_pct, true); + + ibuf_max_size_update(srv_change_buffer_max_size); + + mysql_mutex_init(pending_checkpoint_mutex_key, + &log_requests.mutex, + MY_MUTEX_INIT_FAST); +#ifdef MYSQL_DYNAMIC_PLUGIN + if (innobase_hton != p) { + innobase_hton = reinterpret_cast<handlerton*>(p); + *innobase_hton = *innodb_hton_ptr; + } +#endif /* MYSQL_DYNAMIC_PLUGIN */ + + memset(innodb_counter_value, 0, sizeof innodb_counter_value); + + /* Do this as late as possible so server is fully starts up, + since we might get some initial stats if user choose to turn + on some counters from start up */ + if (innobase_enable_monitor_counter) { + innodb_enable_monitor_at_startup( + innobase_enable_monitor_counter); + } + + /* Turn on monitor counters that are default on */ + srv_mon_default_on(); + + /* Unit Tests */ +#ifdef UNIV_ENABLE_UNIT_TEST_GET_PARENT_DIR + unit_test_os_file_get_parent_dir(); +#endif /* UNIV_ENABLE_UNIT_TEST_GET_PARENT_DIR */ + +#ifdef UNIV_ENABLE_UNIT_TEST_MAKE_FILEPATH + test_make_filepath(); +#endif /*UNIV_ENABLE_UNIT_TEST_MAKE_FILEPATH */ + +#ifdef UNIV_ENABLE_DICT_STATS_TEST + test_dict_stats_all(); +#endif /*UNIV_ENABLE_DICT_STATS_TEST */ + +#ifdef UNIV_ENABLE_UNIT_TEST_ROW_RAW_FORMAT_INT +# ifdef HAVE_UT_CHRONO_T + test_row_raw_format_int(); +# endif /* HAVE_UT_CHRONO_T */ +#endif /* UNIV_ENABLE_UNIT_TEST_ROW_RAW_FORMAT_INT */ + + DBUG_RETURN(0); +} + +/** Shut down the InnoDB storage engine. +@return 0 */ +static +int +innobase_end(handlerton*, ha_panic_function) +{ + DBUG_ENTER("innobase_end"); + + if (srv_was_started) { + THD *thd= current_thd; + if (thd) { // may be UNINSTALL PLUGIN statement + if (trx_t* trx = thd_to_trx(thd)) { + trx->free(); + } + } + + + innodb_shutdown(); + mysql_mutex_destroy(&log_requests.mutex); + } + + DBUG_RETURN(0); +} + +/*****************************************************************//** +Commits a transaction in an InnoDB database. */ +void +innobase_commit_low( +/*================*/ + trx_t* trx) /*!< in: transaction handle */ +{ +#ifdef WITH_WSREP + const char* tmp = 0; + const bool is_wsrep = trx->is_wsrep(); + if (is_wsrep) { + tmp = thd_proc_info(trx->mysql_thd, "innobase_commit_low()"); + } +#endif /* WITH_WSREP */ + if (trx_is_started(trx)) { + trx_commit_for_mysql(trx); + } else { + trx->will_lock = false; +#ifdef WITH_WSREP + trx->wsrep = false; +#endif /* WITH_WSREP */ + } + +#ifdef WITH_WSREP + if (is_wsrep) { + thd_proc_info(trx->mysql_thd, tmp); + } +#endif /* WITH_WSREP */ +} + +/*****************************************************************//** +Creates an InnoDB transaction struct for the thd if it does not yet have one. +Starts a new InnoDB transaction if a transaction is not yet started. And +assigns a new snapshot for a consistent read if the transaction does not yet +have one. +@return 0 */ +static +int +innobase_start_trx_and_assign_read_view( +/*====================================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + THD* thd) /*!< in: MySQL thread handle of the user for + whom the transaction should be committed */ +{ + DBUG_ENTER("innobase_start_trx_and_assign_read_view"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + /* Create a new trx struct for thd, if it does not yet have one */ + + trx_t* trx = check_trx_exists(thd); + + /* The transaction should not be active yet, start it */ + + ut_ad(!trx_is_started(trx)); + + trx_start_if_not_started_xa(trx, false); + + /* Assign a read view if the transaction does not have it yet. + Do this only if transaction is using REPEATABLE READ isolation + level. */ + trx->isolation_level = innobase_map_isolation_level( + thd_get_trx_isolation(thd)); + + if (trx->isolation_level == TRX_ISO_REPEATABLE_READ) { + trx->read_view.open(trx); + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + HA_ERR_UNSUPPORTED, + "InnoDB: WITH CONSISTENT SNAPSHOT" + " was ignored because this phrase" + " can only be used with" + " REPEATABLE READ isolation level."); + } + + /* Set the MySQL flag to mark that there is an active transaction */ + + innobase_register_trx(hton, current_thd, trx); + + DBUG_RETURN(0); +} + +static +void +innobase_commit_ordered_2( +/*======================*/ + trx_t* trx, /*!< in: Innodb transaction */ + THD* thd) /*!< in: MySQL thread handle */ +{ + DBUG_ENTER("innobase_commit_ordered_2"); + + if (trx->id) { + /* The following call reads the binary log position of + the transaction being committed. + + Binary logging of other engines is not relevant to + InnoDB as all InnoDB requires is that committing + InnoDB transactions appear in the same order in the + MySQL binary log as they appear in InnoDB logs, which + is guaranteed by the server. + + If the binary log is not enabled, or the transaction + is not written to the binary log, the file name will + be a NULL pointer. */ + thd_binlog_pos(thd, &trx->mysql_log_file_name, + &trx->mysql_log_offset); + + /* Don't do write + flush right now. For group commit + to work we want to do the flush later. */ + trx->flush_log_later = true; + } + +#ifdef WITH_WSREP + /* If the transaction is not run in 2pc, we must assign wsrep + XID here in order to get it written in rollback segment. */ + if (trx->is_wsrep()) { + thd_get_xid(thd, &reinterpret_cast<MYSQL_XID&>(trx->xid)); + } +#endif /* WITH_WSREP */ + + innobase_commit_low(trx); + trx->mysql_log_file_name = NULL; + trx->flush_log_later = false; + + DBUG_VOID_RETURN; +} + +/*****************************************************************//** +Perform the first, fast part of InnoDB commit. + +Doing it in this call ensures that we get the same commit order here +as in binlog and any other participating transactional storage engines. + +Note that we want to do as little as really needed here, as we run +under a global mutex. The expensive fsync() is done later, in +innobase_commit(), without a lock so group commit can take place. + +Note also that this method can be called from a different thread than +the one handling the rest of the transaction. */ +static +void +innobase_commit_ordered( +/*====================*/ + handlerton *hton, /*!< in: Innodb handlerton */ + THD* thd, /*!< in: MySQL thread handle of the user for whom + the transaction should be committed */ + bool all) /*!< in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +{ + trx_t* trx; + DBUG_ENTER("innobase_commit_ordered"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { + /* We cannot throw error here; instead we will catch this error + again in innobase_commit() and report it from there. */ + DBUG_VOID_RETURN; + } + + /* commit_ordered is only called when committing the whole transaction + (or an SQL statement when autocommit is on). */ + DBUG_ASSERT(all || + (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))); + + innobase_commit_ordered_2(trx, thd); + trx->active_commit_ordered = true; + + DBUG_VOID_RETURN; +} + +/** Mark the end of a statement. +@param trx transaction +@return whether an error occurred */ +static bool end_of_statement(trx_t *trx) +{ + trx_mark_sql_stat_end(trx); + if (UNIV_LIKELY(trx->error_state == DB_SUCCESS)) + return false; + + trx_savept_t savept; + savept.least_undo_no= 0; + trx->rollback(&savept); + /* MariaDB will roll back the entire transaction. */ + trx->bulk_insert= false; + trx->last_sql_stat_start.least_undo_no= 0; + trx->savepoints_discard(); + return true; +} + +/*****************************************************************//** +Commits a transaction in an InnoDB database or marks an SQL statement +ended. +@return 0 or deadlock error if the transaction was aborted by another + higher priority transaction. */ +static +int +innobase_commit( +/*============*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + THD* thd, /*!< in: MySQL thread handle of the + user for whom the transaction should + be committed */ + bool commit_trx) /*!< in: true - commit transaction + false - the current SQL statement + ended */ +{ + DBUG_ENTER("innobase_commit"); + DBUG_PRINT("enter", ("commit_trx: %d", commit_trx)); + DBUG_ASSERT(hton == innodb_hton_ptr); + DBUG_PRINT("trans", ("ending transaction")); + + trx_t* trx = check_trx_exists(thd); + + ut_ad(!trx->dict_operation_lock_mode); + ut_ad(!trx->dict_operation); + + /* Transaction is deregistered only in a commit or a rollback. If + it is deregistered we know there cannot be resources to be freed + and we could return immediately. For the time being, we play safe + and do the cleanup though there should be nothing to clean up. */ + + if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { + + sql_print_error("Transaction not registered for MariaDB 2PC," + " but transaction is active"); + } + + bool read_only = trx->read_only || trx->id == 0; + DBUG_PRINT("info", ("readonly: %d", read_only)); + + if (commit_trx + || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { + + /* Run the fast part of commit if we did not already. */ + if (!trx->active_commit_ordered) { + innobase_commit_ordered_2(trx, thd); + + } + + /* We were instructed to commit the whole transaction, or + this is an SQL statement end and autocommit is on */ + + /* At this point commit order is fixed and transaction is + visible to others. So we can wakeup other commits waiting for + this one, to allow then to group commit with us. */ + thd_wakeup_subsequent_commits(thd, 0); + + /* Now do a write + flush of logs. */ + trx_commit_complete_for_mysql(trx); + + trx_deregister_from_2pc(trx); + } else { + /* We just mark the SQL statement ended and do not do a + transaction commit */ + + /* If we had reserved the auto-inc lock for some + table in this SQL statement we release it now */ + + if (!read_only) { + lock_unlock_table_autoinc(trx); + } + + /* Store the current undo_no of the transaction so that we + know where to roll back if we have to roll back the next + SQL statement */ + if (UNIV_UNLIKELY(end_of_statement(trx))) { + DBUG_RETURN(1); + } + } + + /* Reset the number AUTO-INC rows required */ + trx->n_autoinc_rows = 0; + + /* This is a statement level variable. */ + trx->fts_next_doc_id = 0; + + DBUG_RETURN(0); +} + +/*****************************************************************//** +Rolls back a transaction or the latest SQL statement. +@return 0 or error number */ +static +int +innobase_rollback( +/*==============*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + THD* thd, /*!< in: handle to the MySQL thread + of the user whose transaction should + be rolled back */ + bool rollback_trx) /*!< in: TRUE - rollback entire + transaction FALSE - rollback the current + statement only */ +{ + DBUG_ENTER("innobase_rollback"); + DBUG_ASSERT(hton == innodb_hton_ptr); + DBUG_PRINT("trans", ("aborting transaction")); + + trx_t* trx = check_trx_exists(thd); + + ut_ad(!trx->dict_operation_lock_mode); + ut_ad(!trx->dict_operation); + + /* Reset the number AUTO-INC rows required */ + + trx->n_autoinc_rows = 0; + + /* If we had reserved the auto-inc lock for some table (if + we come here to roll back the latest SQL statement) we + release it now before a possibly lengthy rollback */ + lock_unlock_table_autoinc(trx); + + /* This is a statement level variable. */ + + trx->fts_next_doc_id = 0; + + dberr_t error; + +#ifdef WITH_WSREP + /* If trx was assigned wsrep XID in prepare phase and the + trx is being rolled back due to BF abort, clear XID in order + to avoid writing it to rollback segment out of order. The XID + will be reassigned when the transaction is replayed. */ + if (trx->state != TRX_STATE_NOT_STARTED + && wsrep_is_wsrep_xid(&trx->xid)) { + trx->xid.null(); + } +#endif /* WITH_WSREP */ + if (rollback_trx + || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + + error = trx_rollback_for_mysql(trx); + + trx_deregister_from_2pc(trx); + } else { + + error = trx_rollback_last_sql_stat_for_mysql(trx); + } + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, trx->mysql_thd)); +} + +/*****************************************************************//** +Rolls back a transaction +@return 0 or error number */ +static +int +innobase_rollback_trx( +/*==================*/ + trx_t* trx) /*!< in: transaction */ +{ + DBUG_ENTER("innobase_rollback_trx"); + DBUG_PRINT("trans", ("aborting transaction")); + + /* If we had reserved the auto-inc lock for some table (if + we come here to roll back the latest SQL statement) we + release it now before a possibly lengthy rollback */ + lock_unlock_table_autoinc(trx); + trx_deregister_from_2pc(trx); + + DBUG_RETURN(convert_error_code_to_mysql(trx_rollback_for_mysql(trx), + 0, trx->mysql_thd)); +} + +/** Invoke commit_checkpoint_notify_ha() on completed log flush requests. +@param pending log_requests.start +@param lsn log_sys.get_flushed_lsn() */ +static void log_flush_notify_and_unlock(log_flush_request *pending, lsn_t lsn) +{ + mysql_mutex_assert_owner(&log_requests.mutex); + ut_ad(pending == log_requests.start.load(std::memory_order_relaxed)); + log_flush_request *entry= pending, *last= nullptr; + /* Process the first requests that have been completed. Since + the list is not necessarily in ascending order of LSN, we may + miss to notify some requests that have already been completed. + But there is no harm in delaying notifications for those a bit. + And in practise, the list is unlikely to have more than one + element anyway, because the redo log would be flushed every + srv_flush_log_at_timeout seconds (1 by default). */ + for (; entry && entry->lsn <= lsn; last= entry, entry= entry->next); + + if (!last) + { + mysql_mutex_unlock(&log_requests.mutex); + return; + } + + /* Detach the head of the list that corresponds to persisted log writes. */ + if (!entry) + log_requests.end= entry; + log_requests.start.store(entry, std::memory_order_relaxed); + mysql_mutex_unlock(&log_requests.mutex); + + /* Now that we have released the mutex, notify the submitters + and free the head of the list. */ + do + { + entry= pending; + pending= pending->next; + commit_checkpoint_notify_ha(entry->cookie); + my_free(entry); + } + while (entry != last); +} + +/** Invoke commit_checkpoint_notify_ha() to notify that outstanding +log writes have been completed. */ +void log_flush_notify(lsn_t flush_lsn) +{ + if (auto pending= log_requests.start.load(std::memory_order_acquire)) + { + mysql_mutex_lock(&log_requests.mutex); + pending= log_requests.start.load(std::memory_order_relaxed); + log_flush_notify_and_unlock(pending, flush_lsn); + } +} + +/** Handle a commit checkpoint request from server layer. +We put the request in a queue, so that we can notify upper layer about +checkpoint complete when we have flushed the redo log. +If we have already flushed all relevant redo log, we notify immediately.*/ +static void innodb_log_flush_request(void *cookie) +{ + lsn_t flush_lsn= log_sys.get_flushed_lsn(); + /* Load lsn relaxed after flush_lsn was loaded from the same cache line */ + const lsn_t lsn= log_sys.get_lsn(); + + if (flush_lsn >= lsn) + /* All log is already persistent. */; + else if (UNIV_UNLIKELY(srv_force_recovery >= SRV_FORCE_NO_BACKGROUND)) + /* Normally, srv_master_callback() should periodically invoke + srv_sync_log_buffer_in_background(), which should initiate a log + flush about once every srv_flush_log_at_timeout seconds. But, + starting with the innodb_force_recovery=2 level, that background + task will not run. */ + log_write_up_to(flush_lsn= lsn, true); + else if (log_flush_request *req= static_cast<log_flush_request*> + (my_malloc(PSI_INSTRUMENT_ME, sizeof *req, MYF(MY_WME)))) + { + req->next= nullptr; + req->cookie= cookie; + req->lsn= lsn; + + log_flush_request *start= nullptr; + + mysql_mutex_lock(&log_requests.mutex); + /* In order to prevent a race condition where log_flush_notify() + would skip a notification due to, we must update log_requests.start from + nullptr (empty) to the first req using std::memory_order_release. */ + if (log_requests.start.compare_exchange_strong(start, req, + std::memory_order_release, + std::memory_order_relaxed)) + { + ut_ad(!log_requests.end); + start= req; + /* In case log_flush_notify() executed + log_requests.start.load(std::memory_order_acquire) right before + our successful compare_exchange, we must re-read flush_lsn to + ensure that our request will be notified immediately if applicable. */ + flush_lsn= log_sys.get_flushed_lsn(); + } + else + { + /* Append the entry to the list. Because we determined req->lsn before + acquiring the mutex, this list may not be ordered by req->lsn, + even though log_flush_notify_and_unlock() assumes so. */ + log_requests.end->next= req; + } + + log_requests.end= req; + + /* This hopefully addresses the hang that was reported in MDEV-24302. + Upon receiving a new request, we will notify old requests of + completion. */ + log_flush_notify_and_unlock(start, flush_lsn); + return; + } + else + sql_print_error("Failed to allocate %zu bytes." + " Commit checkpoint will be skipped.", sizeof *req); + + /* This hopefully addresses the hang that was reported in MDEV-24302. + Upon receiving a new request to notify of log writes becoming + persistent, we will notify old requests of completion. Note: + log_flush_notify() may skip some notifications because it is + basically assuming that the list is in ascending order of LSN. */ + log_flush_notify(flush_lsn); + commit_checkpoint_notify_ha(cookie); +} + +/*****************************************************************//** +Rolls back a transaction to a savepoint. +@return 0 if success, HA_ERR_NO_SAVEPOINT if no savepoint with the +given name */ +static +int +innobase_rollback_to_savepoint( +/*===========================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + THD* thd, /*!< in: handle to the MySQL thread + of the user whose transaction should + be rolled back to savepoint */ + void* savepoint) /*!< in: savepoint data */ +{ + + DBUG_ENTER("innobase_rollback_to_savepoint"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx_t* trx = check_trx_exists(thd); + + /* TODO: use provided savepoint data area to store savepoint data */ + + char name[64]; + + longlong2str(longlong(savepoint), name, 36); + + int64_t mysql_binlog_cache_pos; + + dberr_t error = trx_rollback_to_savepoint_for_mysql( + trx, name, &mysql_binlog_cache_pos); + + if (error == DB_SUCCESS && trx->fts_trx != NULL) { + fts_savepoint_rollback(trx, name); + } + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + +/*****************************************************************//** +Check whether innodb state allows to safely release MDL locks after +rollback to savepoint. +When binlog is on, MDL locks acquired after savepoint unit are not +released if there are any locks held in InnoDB. +@return true if it is safe, false if its not safe. */ +static +bool +innobase_rollback_to_savepoint_can_release_mdl( +/*===========================================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + THD* thd) /*!< in: handle to the MySQL thread + of the user whose transaction should + be rolled back to savepoint */ +{ + DBUG_ENTER("innobase_rollback_to_savepoint_can_release_mdl"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx_t* trx = check_trx_exists(thd); + + /* If transaction has not acquired any locks then it is safe + to release MDL after rollback to savepoint */ + if (UT_LIST_GET_LEN(trx->lock.trx_locks) == 0) { + + DBUG_RETURN(true); + } + + DBUG_RETURN(false); +} + +/*****************************************************************//** +Release transaction savepoint name. +@return 0 if success, HA_ERR_NO_SAVEPOINT if no savepoint with the +given name */ +static +int +innobase_release_savepoint( +/*=======================*/ + handlerton* hton, /*!< in: handlerton for InnoDB */ + THD* thd, /*!< in: handle to the MySQL thread + of the user whose transaction's + savepoint should be released */ + void* savepoint) /*!< in: savepoint data */ +{ + dberr_t error; + trx_t* trx; + char name[64]; + + DBUG_ENTER("innobase_release_savepoint"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + /* TODO: use provided savepoint data area to store savepoint data */ + + longlong2str(longlong(savepoint), name, 36); + + error = trx_release_savepoint_for_mysql(trx, name); + + if (error == DB_SUCCESS && trx->fts_trx != NULL) { + fts_savepoint_release(trx, name); + } + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + +/*****************************************************************//** +Sets a transaction savepoint. +@return always 0, that is, always succeeds */ +static +int +innobase_savepoint( +/*===============*/ + handlerton* hton, /*!< in: handle to the InnoDB handlerton */ + THD* thd, /*!< in: handle to the MySQL thread */ + void* savepoint)/*!< in: savepoint data */ +{ + DBUG_ENTER("innobase_savepoint"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + /* In the autocommit mode there is no sense to set a savepoint + (unless we are in sub-statement), so SQL layer ensures that + this method is never called in such situation. */ + + trx_t* trx = check_trx_exists(thd); + + /* Cannot happen outside of transaction */ + DBUG_ASSERT(trx_is_registered_for_2pc(trx)); + + /* TODO: use provided savepoint data area to store savepoint data */ + char name[64]; + + longlong2str(longlong(savepoint), name, 36); + + dberr_t error = trx_savepoint_for_mysql(trx, name, 0); + + if (error == DB_SUCCESS && trx->fts_trx != NULL) { + fts_savepoint_take(trx->fts_trx, name); + } + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + + +/** + Frees a possible InnoDB trx object associated with the current THD. + + @param hton innobase handlerton + @param thd server thread descriptor, which resources should be free'd + + @return 0 always +*/ +static int innobase_close_connection(handlerton *hton, THD *thd) +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + if (auto trx= thd_to_trx(thd)) + { + thd_set_ha_data(thd, innodb_hton_ptr, NULL); + if (trx->state == TRX_STATE_PREPARED && trx->has_logged_persistent()) + { + trx_disconnect_prepared(trx); + return 0; + } + innobase_rollback_trx(trx); + trx->free(); + DEBUG_SYNC(thd, "innobase_connection_closed"); + } + return 0; +} + +/** Cancel any pending lock request associated with the current THD. +@sa THD::awake() @sa ha_kill_query() */ +static void innobase_kill_query(handlerton*, THD *thd, enum thd_kill_levels) +{ + DBUG_ENTER("innobase_kill_query"); + + if (trx_t* trx= thd_to_trx(thd)) + { + ut_ad(trx->mysql_thd == thd); + mysql_mutex_lock(&lock_sys.wait_mutex); + lock_t *lock= trx->lock.wait_lock; + + if (!lock) + /* The transaction is not waiting for any lock. */; +#ifdef WITH_WSREP + else if (trx->is_wsrep() && wsrep_thd_is_aborting(thd)) + /* if victim has been signaled by BF thread and/or aborting is already + progressing, following query aborting is not necessary any more. + Also, BF thread should own trx mutex for the victim. */; +#endif /* WITH_WSREP */ + else + { + if (!trx->dict_operation) + { + /* Dictionary transactions must be immune to KILL, because they + may be executed as part of a multi-transaction DDL operation, such + as rollback_inplace_alter_table() or ha_innobase::delete_table(). */; + trx->error_state= DB_INTERRUPTED; + lock_sys_t::cancel<false>(trx, lock); + } + lock_sys.deadlock_check(); + } + mysql_mutex_unlock(&lock_sys.wait_mutex); + } + + DBUG_VOID_RETURN; +} + + +/*************************************************************************//** +** InnoDB database tables +*****************************************************************************/ + +/** Get the record format from the data dictionary. +@return one of ROW_TYPE_REDUNDANT, ROW_TYPE_COMPACT, +ROW_TYPE_COMPRESSED, ROW_TYPE_DYNAMIC */ + +enum row_type +ha_innobase::get_row_type() const +{ + if (m_prebuilt && m_prebuilt->table) { + const ulint flags = m_prebuilt->table->flags; + + switch (dict_tf_get_rec_format(flags)) { + case REC_FORMAT_REDUNDANT: + return(ROW_TYPE_REDUNDANT); + case REC_FORMAT_COMPACT: + return(ROW_TYPE_COMPACT); + case REC_FORMAT_COMPRESSED: + return(ROW_TYPE_COMPRESSED); + case REC_FORMAT_DYNAMIC: + return(ROW_TYPE_DYNAMIC); + } + } + ut_ad(0); + return(ROW_TYPE_NOT_USED); +} + +/****************************************************************//** +Get the table flags to use for the statement. +@return table flags */ + +handler::Table_flags +ha_innobase::table_flags() const +/*============================*/ +{ + THD* thd = ha_thd(); + handler::Table_flags flags = m_int_table_flags; + + /* Need to use tx_isolation here since table flags is (also) + called before prebuilt is inited. */ + + if (thd_tx_isolation(thd) <= ISO_READ_COMMITTED) { + return(flags); + } + + return(flags | HA_BINLOG_STMT_CAPABLE); +} + +/****************************************************************//** +Returns the table type (storage engine name). +@return table type */ + +const char* +ha_innobase::table_type() const +/*===========================*/ +{ + return(innobase_hton_name); +} + +/****************************************************************//** +Returns the index type. +@return index type */ + +const char* +ha_innobase::index_type( +/*====================*/ + uint keynr) /*!< : index number */ +{ + dict_index_t* index = innobase_get_index(keynr); + + if (!index) { + return "Corrupted"; + } + + if (index->type & DICT_FTS) { + return("FULLTEXT"); + } + + if (dict_index_is_spatial(index)) { + return("SPATIAL"); + } + + return("BTREE"); +} + +/****************************************************************//** +Returns the operations supported for indexes. +@return flags of supported operations */ + +ulong +ha_innobase::index_flags( +/*=====================*/ + uint key, + uint, + bool) const +{ + if (table_share->key_info[key].algorithm == HA_KEY_ALG_FULLTEXT) { + return(0); + } + + /* For spatial index, we don't support descending scan + and ICP so far. */ + if (table_share->key_info[key].flags & HA_SPATIAL) { + return HA_READ_NEXT | HA_READ_ORDER| HA_READ_RANGE + | HA_KEYREAD_ONLY | HA_KEY_SCAN_NOT_ROR; + } + + ulong flags= key == table_share->primary_key + ? HA_CLUSTERED_INDEX : 0; + + flags |= HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER + | HA_READ_RANGE | HA_KEYREAD_ONLY + | HA_DO_INDEX_COND_PUSHDOWN + | HA_DO_RANGE_FILTER_PUSHDOWN; + + return(flags); +} + +/****************************************************************//** +Returns the maximum number of keys. +@return MAX_KEY */ + +uint +ha_innobase::max_supported_keys() const +/*===================================*/ +{ + return(MAX_KEY); +} + +/****************************************************************//** +Returns the maximum key length. +@return maximum supported key length, in bytes */ + +uint +ha_innobase::max_supported_key_length() const +/*=========================================*/ +{ + /* An InnoDB page must store >= 2 keys; a secondary key record + must also contain the primary key value. Therefore, if both + the primary key and the secondary key are at this maximum length, + it must be less than 1/4th of the free space on a page including + record overhead. + + MySQL imposes its own limit to this number; MAX_KEY_LENGTH = 3072. + + For page sizes = 16k, InnoDB historically reported 3500 bytes here, + But the MySQL limit of 3072 was always used through the handler + interface. + + Note: Handle 16k and 32k pages the same here since the limits + are higher than imposed by MySQL. */ + + switch (srv_page_size) { + case 4096: + /* Hack: allow mysql.innodb_index_stats to be created. */ + /* FIXME: rewrite this API, and in sql_table.cc consider + that in index-organized tables (such as InnoDB), secondary + index records will be padded with the PRIMARY KEY, instead + of some short ROWID or record heap address. */ + return(1173); + case 8192: + return(1536); + default: + return(3500); + } +} + +/****************************************************************//** +Returns the key map of keys that are usable for scanning. +@return key_map_full */ + +const key_map* +ha_innobase::keys_to_use_for_scanning() +/*===================================*/ +{ + return(&key_map_full); +} + +/** Ensure that indexed virtual columns will be computed. */ +void ha_innobase::column_bitmaps_signal() +{ + if (!table->vfield || table->current_lock != F_WRLCK) + return; + + dict_index_t* clust_index= dict_table_get_first_index(m_prebuilt->table); + uint num_v= 0; + for (uint j = 0; j < table->s->virtual_fields; j++) + { + if (table->vfield[j]->stored_in_db()) + continue; + + dict_col_t *col= &m_prebuilt->table->v_cols[num_v].m_col; + if (col->ord_part || + (dict_index_is_online_ddl(clust_index) && + row_log_col_is_indexed(clust_index, num_v))) + table->mark_virtual_column_with_deps(table->vfield[j]); + num_v++; + } +} + + +/****************************************************************//** +Determines if table caching is supported. +@return HA_CACHE_TBL_ASKTRANSACT */ + +uint8 +ha_innobase::table_cache_type() +/*===========================*/ +{ + return(HA_CACHE_TBL_ASKTRANSACT); +} + +/** Normalizes a table name string. +A normalized name consists of the database name catenated to '/' +and table name. For example: test/mytable. +On Windows, normalization puts both the database name and the +table name always to lower case if "set_lower_case" is set to TRUE. +@param[out] norm_name Normalized name, null-terminated. +@param[in] name Name to normalize. +@param[in] set_lower_case True if we also should fold to lower case. */ +void +normalize_table_name_c_low( +/*=======================*/ + char* norm_name, /* out: normalized name as a + null-terminated string */ + const char* name, /* in: table name string */ + bool set_lower_case) /* in: TRUE if we want to set + name to lower case */ +{ + char* name_ptr; + ulint name_len; + char* db_ptr; + ulint db_len; + char* ptr; + ulint norm_len; + + /* Scan name from the end */ + + ptr = strend(name) - 1; + + /* seek to the last path separator */ + while (ptr >= name && *ptr != '\\' && *ptr != '/') { + ptr--; + } + + name_ptr = ptr + 1; + name_len = strlen(name_ptr); + + /* skip any number of path separators */ + while (ptr >= name && (*ptr == '\\' || *ptr == '/')) { + ptr--; + } + + DBUG_ASSERT(ptr >= name); + + /* seek to the last but one path separator or one char before + the beginning of name */ + db_len = 0; + while (ptr >= name && *ptr != '\\' && *ptr != '/') { + ptr--; + db_len++; + } + + db_ptr = ptr + 1; + + norm_len = db_len + name_len + sizeof "/"; + ut_a(norm_len < FN_REFLEN - 1); + + memcpy(norm_name, db_ptr, db_len); + + norm_name[db_len] = '/'; + + /* Copy the name and null-byte. */ + memcpy(norm_name + db_len + 1, name_ptr, name_len + 1); + + if (set_lower_case) { + innobase_casedn_str(norm_name); + } +} + +create_table_info_t::create_table_info_t( + THD* thd, + const TABLE* form, + HA_CREATE_INFO* create_info, + char* table_name, + char* remote_path, + bool file_per_table, + trx_t* trx) + : m_thd(thd), + m_trx(trx), + m_form(form), + m_default_row_format(innodb_default_row_format), + m_create_info(create_info), + m_table_name(table_name), m_table(NULL), + m_remote_path(remote_path), + m_innodb_file_per_table(file_per_table) +{ +} + +#if !defined(DBUG_OFF) +/********************************************************************* +Test normalize_table_name_low(). */ +static +void +test_normalize_table_name_low() +/*===========================*/ +{ + char norm_name[FN_REFLEN]; + const char* test_data[][2] = { + /* input, expected result */ + {"./mysqltest/t1", "mysqltest/t1"}, + {"./test/#sql-842b_2", "test/#sql-842b_2"}, + {"./test/#sql-85a3_10", "test/#sql-85a3_10"}, + {"./test/#sql2-842b-2", "test/#sql2-842b-2"}, + {"./test/bug29807", "test/bug29807"}, + {"./test/foo", "test/foo"}, + {"./test/innodb_bug52663", "test/innodb_bug52663"}, + {"./test/t", "test/t"}, + {"./test/t1", "test/t1"}, + {"./test/t10", "test/t10"}, + {"/a/b/db/table", "db/table"}, + {"/a/b/db///////table", "db/table"}, + {"/a/b////db///////table", "db/table"}, + {"/var/tmp/mysqld.1/#sql842b_2_10", "mysqld.1/#sql842b_2_10"}, + {"db/table", "db/table"}, + {"ddd/t", "ddd/t"}, + {"d/ttt", "d/ttt"}, + {"d/t", "d/t"}, + {".\\mysqltest\\t1", "mysqltest/t1"}, + {".\\test\\#sql-842b_2", "test/#sql-842b_2"}, + {".\\test\\#sql-85a3_10", "test/#sql-85a3_10"}, + {".\\test\\#sql2-842b-2", "test/#sql2-842b-2"}, + {".\\test\\bug29807", "test/bug29807"}, + {".\\test\\foo", "test/foo"}, + {".\\test\\innodb_bug52663", "test/innodb_bug52663"}, + {".\\test\\t", "test/t"}, + {".\\test\\t1", "test/t1"}, + {".\\test\\t10", "test/t10"}, + {"C:\\a\\b\\db\\table", "db/table"}, + {"C:\\a\\b\\db\\\\\\\\\\\\\\table", "db/table"}, + {"C:\\a\\b\\\\\\\\db\\\\\\\\\\\\\\table", "db/table"}, + {"C:\\var\\tmp\\mysqld.1\\#sql842b_2_10", "mysqld.1/#sql842b_2_10"}, + {"db\\table", "db/table"}, + {"ddd\\t", "ddd/t"}, + {"d\\ttt", "d/ttt"}, + {"d\\t", "d/t"}, + }; + + for (size_t i = 0; i < UT_ARR_SIZE(test_data); i++) { + printf("test_normalize_table_name_low():" + " testing \"%s\", expected \"%s\"... ", + test_data[i][0], test_data[i][1]); + + normalize_table_name_c_low( + norm_name, test_data[i][0], FALSE); + + if (strcmp(norm_name, test_data[i][1]) == 0) { + printf("ok\n"); + } else { + printf("got \"%s\"\n", norm_name); + ut_error; + } + } +} + +/********************************************************************* +Test ut_format_name(). */ +static +void +test_ut_format_name() +/*=================*/ +{ + char buf[NAME_LEN * 3]; + + struct { + const char* name; + ulint buf_size; + const char* expected; + } test_data[] = { + {"test/t1", sizeof(buf), "`test`.`t1`"}, + {"test/t1", 12, "`test`.`t1`"}, + {"test/t1", 11, "`test`.`t1"}, + {"test/t1", 10, "`test`.`t"}, + {"test/t1", 9, "`test`.`"}, + {"test/t1", 8, "`test`."}, + {"test/t1", 7, "`test`"}, + {"test/t1", 6, "`test"}, + {"test/t1", 5, "`tes"}, + {"test/t1", 4, "`te"}, + {"test/t1", 3, "`t"}, + {"test/t1", 2, "`"}, + {"test/t1", 1, ""}, + {"test/t1", 0, "BUF_NOT_CHANGED"}, + {"table", sizeof(buf), "`table`"}, + {"ta'le", sizeof(buf), "`ta'le`"}, + {"ta\"le", sizeof(buf), "`ta\"le`"}, + {"ta`le", sizeof(buf), "`ta``le`"}, + }; + + for (size_t i = 0; i < UT_ARR_SIZE(test_data); i++) { + + memcpy(buf, "BUF_NOT_CHANGED", strlen("BUF_NOT_CHANGED") + 1); + + char* ret; + + ret = ut_format_name(test_data[i].name, + buf, + test_data[i].buf_size); + + ut_a(ret == buf); + + if (strcmp(buf, test_data[i].expected) == 0) { + ib::info() << "ut_format_name(" << test_data[i].name + << ", buf, " << test_data[i].buf_size << ")," + " expected " << test_data[i].expected + << ", OK"; + } else { + ib::error() << "ut_format_name(" << test_data[i].name + << ", buf, " << test_data[i].buf_size << ")," + " expected " << test_data[i].expected + << ", ERROR: got " << buf; + ut_error; + } + } +} +#endif /* !DBUG_OFF */ + +/** Match index columns between MySQL and InnoDB. +This function checks whether the index column information +is consistent between KEY info from mysql and that from innodb index. +@param[in] key_info Index info from mysql +@param[in] index_info Index info from InnoDB +@return true if all column types match. */ +static +bool +innobase_match_index_columns( + const KEY* key_info, + const dict_index_t* index_info) +{ + const KEY_PART_INFO* key_part; + const KEY_PART_INFO* key_end; + const dict_field_t* innodb_idx_fld; + const dict_field_t* innodb_idx_fld_end; + + DBUG_ENTER("innobase_match_index_columns"); + + /* Check whether user defined index column count matches */ + if (key_info->user_defined_key_parts != + index_info->n_user_defined_cols) { + DBUG_RETURN(FALSE); + } + + key_part = key_info->key_part; + key_end = key_part + key_info->user_defined_key_parts; + innodb_idx_fld = index_info->fields; + innodb_idx_fld_end = index_info->fields + index_info->n_fields; + + /* Check each index column's datatype. We do not check + column name because there exists case that index + column name got modified in mysql but such change does not + propagate to InnoDB. + One hidden assumption here is that the index column sequences + are matched up between those in mysql and InnoDB. */ + for (; key_part != key_end; ++key_part) { + unsigned is_unsigned; + auto mtype = innodb_idx_fld->col->mtype; + + /* Need to translate to InnoDB column type before + comparison. */ + auto col_type = get_innobase_type_from_mysql_type( + &is_unsigned, key_part->field); + + /* Ignore InnoDB specific system columns. */ + while (mtype == DATA_SYS) { + innodb_idx_fld++; + + if (innodb_idx_fld >= innodb_idx_fld_end) { + DBUG_RETURN(FALSE); + } + } + + /* MariaDB-5.5 compatibility */ + if ((key_part->field->real_type() == MYSQL_TYPE_ENUM || + key_part->field->real_type() == MYSQL_TYPE_SET) && + mtype == DATA_FIXBINARY) { + col_type= DATA_FIXBINARY; + } + + if (innodb_idx_fld->descending + != !!(key_part->key_part_flag & HA_REVERSE_SORT)) { + DBUG_RETURN(FALSE); + } + + if (col_type != mtype) { + /* If the col_type we get from mysql type is a geometry + data type, we should check if mtype is a legacy type + from 5.6, either upgraded to DATA_GEOMETRY or not. + This is indeed not an accurate check, but should be + safe, since DATA_BLOB would be upgraded once we create + spatial index on it and we intend to use DATA_GEOMETRY + for legacy GIS data types which are of var-length. */ + switch (col_type) { + case DATA_GEOMETRY: + if (mtype == DATA_BLOB) { + break; + } + /* Fall through */ + default: + /* Column type mismatches */ + DBUG_RETURN(false); + } + } + + innodb_idx_fld++; + } + + DBUG_RETURN(TRUE); +} + +/** Build a template for a base column for a virtual column +@param[in] table MySQL TABLE +@param[in] clust_index InnoDB clustered index +@param[in] field field in MySQL table +@param[in] col InnoDB column +@param[in,out] templ template to fill +@param[in] col_no field index for virtual col +*/ +static +void +innobase_vcol_build_templ( + const TABLE* table, + dict_index_t* clust_index, + Field* field, + const dict_col_t* col, + mysql_row_templ_t* templ, + ulint col_no) +{ + templ->col_no = col_no; + templ->is_virtual = col->is_virtual(); + + if (templ->is_virtual) { + templ->clust_rec_field_no = ULINT_UNDEFINED; + templ->rec_field_no = col->ind; + } else { + templ->clust_rec_field_no = dict_col_get_clust_pos( + col, clust_index); + ut_a(templ->clust_rec_field_no != ULINT_UNDEFINED); + + templ->rec_field_no = templ->clust_rec_field_no; + } + + if (field->real_maybe_null()) { + templ->mysql_null_byte_offset = + field->null_offset(); + + templ->mysql_null_bit_mask = (ulint) field->null_bit; + } else { + templ->mysql_null_bit_mask = 0; + } + + templ->mysql_col_offset = static_cast<ulint>( + get_field_offset(table, field)); + templ->mysql_col_len = static_cast<ulint>(field->pack_length()); + templ->type = col->mtype; + templ->mysql_type = static_cast<ulint>(field->type()); + + if (templ->mysql_type == DATA_MYSQL_TRUE_VARCHAR) { + templ->mysql_length_bytes = static_cast<ulint>( + ((Field_varstring*) field)->length_bytes); + } + + templ->charset = dtype_get_charset_coll(col->prtype); + templ->mbminlen = dict_col_get_mbminlen(col); + templ->mbmaxlen = dict_col_get_mbmaxlen(col); + templ->is_unsigned = col->prtype & DATA_UNSIGNED; +} + +/** Build template for the virtual columns and their base columns. This +is done when the table first opened. +@param[in] table MySQL TABLE +@param[in] ib_table InnoDB dict_table_t +@param[in,out] s_templ InnoDB template structure +@param[in] add_v new virtual columns added along with + add index call +@param[in] locked true if dict_sys.latch is held */ +void +innobase_build_v_templ( + const TABLE* table, + const dict_table_t* ib_table, + dict_vcol_templ_t* s_templ, + const dict_add_v_col_t* add_v, + bool locked) +{ + ulint ncol = unsigned(ib_table->n_cols) - DATA_N_SYS_COLS; + ulint n_v_col = ib_table->n_v_cols; + bool marker[REC_MAX_N_FIELDS]; + + DBUG_ENTER("innobase_build_v_templ"); + ut_ad(ncol < REC_MAX_N_FIELDS); + + if (add_v != NULL) { + n_v_col += add_v->n_v_col; + } + + ut_ad(n_v_col > 0); + + if (!locked) { + dict_sys.lock(SRW_LOCK_CALL); + } + +#if 0 + /* This does not (need to) hold for ctx->new_table in + alter_rebuild_apply_log() */ + ut_ad(dict_sys.locked()); +#endif + + if (s_templ->vtempl) { + if (!locked) { + dict_sys.unlock(); + } + DBUG_VOID_RETURN; + } + + memset(marker, 0, sizeof(bool) * ncol); + + s_templ->vtempl = static_cast<mysql_row_templ_t**>( + ut_zalloc_nokey((ncol + n_v_col) + * sizeof *s_templ->vtempl)); + s_templ->n_col = ncol; + s_templ->n_v_col = n_v_col; + s_templ->rec_len = table->s->reclength; + s_templ->default_rec = UT_NEW_ARRAY_NOKEY(uchar, s_templ->rec_len); + memcpy(s_templ->default_rec, table->s->default_values, s_templ->rec_len); + + /* Mark those columns could be base columns */ + for (ulint i = 0; i < ib_table->n_v_cols; i++) { + const dict_v_col_t* vcol = dict_table_get_nth_v_col( + ib_table, i); + + for (ulint j = vcol->num_base; j--; ) { + marker[vcol->base_col[j]->ind] = true; + } + } + + if (add_v) { + for (ulint i = 0; i < add_v->n_v_col; i++) { + const dict_v_col_t* vcol = &add_v->v_col[i]; + + for (ulint j = vcol->num_base; j--; ) { + marker[vcol->base_col[j]->ind] = true; + } + } + } + + ulint j = 0; + ulint z = 0; + + dict_index_t* clust_index = dict_table_get_first_index(ib_table); + + for (ulint i = 0; i < table->s->fields; i++) { + Field* field = table->field[i]; + + /* Build template for virtual columns */ + if (!field->stored_in_db()) { +#ifdef UNIV_DEBUG + const char* name; + + if (z >= ib_table->n_v_def) { + name = add_v->v_col_name[z - ib_table->n_v_def]; + } else { + name = dict_table_get_v_col_name(ib_table, z); + } + + ut_ad(!my_strcasecmp(system_charset_info, name, + field->field_name.str)); +#endif + const dict_v_col_t* vcol; + + if (z >= ib_table->n_v_def) { + vcol = &add_v->v_col[z - ib_table->n_v_def]; + } else { + vcol = dict_table_get_nth_v_col(ib_table, z); + } + + s_templ->vtempl[z + s_templ->n_col] + = static_cast<mysql_row_templ_t*>( + ut_malloc_nokey( + sizeof *s_templ->vtempl[j])); + + innobase_vcol_build_templ( + table, clust_index, field, + &vcol->m_col, + s_templ->vtempl[z + s_templ->n_col], + z); + z++; + continue; + } + + ut_ad(j < ncol); + + /* Build template for base columns */ + if (marker[j]) { + dict_col_t* col = dict_table_get_nth_col( + ib_table, j); + + ut_ad(!my_strcasecmp(system_charset_info, + dict_table_get_col_name( + ib_table, j), + field->field_name.str)); + + s_templ->vtempl[j] = static_cast< + mysql_row_templ_t*>( + ut_malloc_nokey( + sizeof *s_templ->vtempl[j])); + + innobase_vcol_build_templ( + table, clust_index, field, col, + s_templ->vtempl[j], j); + } + + j++; + } + + if (!locked) { + dict_sys.unlock(); + } + + s_templ->db_name = table->s->db.str; + s_templ->tb_name = table->s->table_name.str; + DBUG_VOID_RETURN; +} + +/** Check consistency between .frm indexes and InnoDB indexes. +@param[in] table table object formed from .frm +@param[in] ib_table InnoDB table definition +@retval true if not errors were found */ +static bool +check_index_consistency(const TABLE* table, const dict_table_t* ib_table) +{ + ulint mysql_num_index = table->s->keys; + ulint ib_num_index = UT_LIST_GET_LEN(ib_table->indexes); + bool ret = true; + + /* If there exists inconsistency between MySQL and InnoDB dictionary + (metadata) information, the number of index defined in MySQL + could exceed that in InnoDB, return error */ + if (ib_num_index < mysql_num_index) { + ret = false; + goto func_exit; + } + + /* For each index in the mysql key_info array, fetch its + corresponding InnoDB index pointer into index_mapping + array. */ + for (ulint count = 0; count < mysql_num_index; count++) { + const dict_index_t* index = dict_table_get_index_on_name( + ib_table, table->key_info[count].name.str); + + if (index == NULL) { + sql_print_error("Cannot find index %s in InnoDB" + " index dictionary.", + table->key_info[count].name.str); + ret = false; + goto func_exit; + } + + /* Double check fetched index has the same + column info as those in mysql key_info. */ + if (!innobase_match_index_columns(&table->key_info[count], + index)) { + sql_print_error("Found index %s whose column info" + " does not match that of MariaDB.", + table->key_info[count].name.str); + ret = false; + goto func_exit; + } + } + +func_exit: + return ret; +} + +/********************************************************************//** +Get the upper limit of the MySQL integral and floating-point type. +@return maximum allowed value for the field */ +ulonglong innobase_get_int_col_max_value(const Field *field) +{ + ulonglong max_value = 0; + + switch (field->key_type()) { + /* TINY */ + case HA_KEYTYPE_BINARY: + max_value = 0xFFULL; + break; + case HA_KEYTYPE_INT8: + max_value = 0x7FULL; + break; + /* SHORT */ + case HA_KEYTYPE_USHORT_INT: + max_value = 0xFFFFULL; + break; + case HA_KEYTYPE_SHORT_INT: + max_value = 0x7FFFULL; + break; + /* MEDIUM */ + case HA_KEYTYPE_UINT24: + max_value = 0xFFFFFFULL; + break; + case HA_KEYTYPE_INT24: + max_value = 0x7FFFFFULL; + break; + /* LONG */ + case HA_KEYTYPE_ULONG_INT: + max_value = 0xFFFFFFFFULL; + break; + case HA_KEYTYPE_LONG_INT: + max_value = 0x7FFFFFFFULL; + break; + /* BIG */ + case HA_KEYTYPE_ULONGLONG: + max_value = 0xFFFFFFFFFFFFFFFFULL; + break; + case HA_KEYTYPE_LONGLONG: + max_value = 0x7FFFFFFFFFFFFFFFULL; + break; + case HA_KEYTYPE_FLOAT: + /* We use the maximum as per IEEE754-2008 standard, 2^24 */ + max_value = 0x1000000ULL; + break; + case HA_KEYTYPE_DOUBLE: + /* We use the maximum as per IEEE754-2008 standard, 2^53 */ + max_value = 0x20000000000000ULL; + break; + default: + ut_error; + } + + return(max_value); +} + +/** Initialize the AUTO_INCREMENT column metadata. + +Since a partial table definition for a persistent table can already be +present in the InnoDB dict_sys cache before it is accessed from SQL, +we have to initialize the AUTO_INCREMENT counter on the first +ha_innobase::open(). + +@param[in,out] table persistent table +@param[in] field the AUTO_INCREMENT column */ +static +void +initialize_auto_increment(dict_table_t* table, const Field* field) +{ + ut_ad(!table->is_temporary()); + + const unsigned col_no = innodb_col_no(field); + + table->autoinc_mutex.wr_lock(); + + table->persistent_autoinc = static_cast<uint16_t>( + dict_table_get_nth_col_pos(table, col_no, NULL) + 1) + & dict_index_t::MAX_N_FIELDS; + + if (table->autoinc) { + /* Already initialized. Our caller checked + table->persistent_autoinc without + autoinc_mutex protection, and there might be multiple + ha_innobase::open() executing concurrently. */ + } else if (srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN) { + /* If the recovery level is set so high that writes + are disabled we force the AUTOINC counter to 0 + value effectively disabling writes to the table. + Secondly, we avoid reading the table in case the read + results in failure due to a corrupted table/index. + + We will not return an error to the client, so that the + tables can be dumped with minimal hassle. If an error + were returned in this case, the first attempt to read + the table would fail and subsequent SELECTs would succeed. */ + } else if (table->persistent_autoinc) { + table->autoinc = innobase_next_autoinc( + btr_read_autoinc_with_fallback(table, col_no), + 1 /* need */, + 1 /* auto_increment_increment */, + 0 /* auto_increment_offset */, + innobase_get_int_col_max_value(field)); + } + + table->autoinc_mutex.wr_unlock(); +} + +/** Open an InnoDB table +@param[in] name table name +@return error code +@retval 0 on success */ +int +ha_innobase::open(const char* name, int, uint) +{ + char norm_name[FN_REFLEN]; + + DBUG_ENTER("ha_innobase::open"); + + normalize_table_name(norm_name, name); + + m_user_thd = NULL; + + /* Will be allocated if it is needed in ::update_row() */ + m_upd_buf = NULL; + m_upd_buf_size = 0; + + char* is_part = is_partition(norm_name); + THD* thd = ha_thd(); + dict_table_t* ib_table = open_dict_table(name, norm_name, is_part, + DICT_ERR_IGNORE_FK_NOKEY); + + DEBUG_SYNC(thd, "ib_open_after_dict_open"); + + if (NULL == ib_table) { + + if (is_part) { + sql_print_error("Failed to open table %s.\n", + norm_name); + } + set_my_errno(ENOENT); + + DBUG_RETURN(HA_ERR_NO_SUCH_TABLE); + } + + size_t n_fields = omits_virtual_cols(*table_share) + ? table_share->stored_fields : table_share->fields; + size_t n_cols = dict_table_get_n_user_cols(ib_table) + + dict_table_get_n_v_cols(ib_table) + - !!DICT_TF2_FLAG_IS_SET(ib_table, DICT_TF2_FTS_HAS_DOC_ID); + + if (UNIV_UNLIKELY(n_cols != n_fields)) { + ib::warn() << "Table " << norm_name << " contains " + << n_cols << " user" + " defined columns in InnoDB, but " << n_fields + << " columns in MariaDB. Please check" + " INFORMATION_SCHEMA.INNODB_SYS_COLUMNS and" + " https://mariadb.com/kb/en/innodb-data-dictionary-troubleshooting/" + " for how to resolve the issue."; + + /* Mark this table as corrupted, so the drop table + or force recovery can still use it, but not others. */ + ib_table->file_unreadable = true; + ib_table->corrupted = true; + ib_table->release(); + set_my_errno(ENOENT); + DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); + } + + innobase_copy_frm_flags_from_table_share(ib_table, table->s); + + MONITOR_INC(MONITOR_TABLE_OPEN); + + if ((ib_table->flags2 & DICT_TF2_DISCARDED)) { + /* Allow an open because a proper DISCARD should have set + all the flags and index root page numbers to FIL_NULL that + should prevent any DML from running but it should allow DDL + operations. */ + } else if (!ib_table->is_readable()) { + const fil_space_t* space = ib_table->space; + if (!space) { + ib_senderrf( + thd, IB_LOG_LEVEL_WARN, + ER_TABLESPACE_MISSING, norm_name); + } + + if (!thd_tablespace_op(thd)) { + set_my_errno(ENOENT); + int ret_err = HA_ERR_TABLESPACE_MISSING; + + if (space && space->crypt_data + && space->crypt_data->is_encrypted()) { + push_warning_printf( + thd, + Sql_condition::WARN_LEVEL_WARN, + HA_ERR_DECRYPTION_FAILED, + "Table %s in file %s is encrypted" + " but encryption service or" + " used key_id %u is not available. " + " Can't continue reading table.", + table_share->table_name.str, + space->chain.start->name, + space->crypt_data->key_id); + ret_err = HA_ERR_DECRYPTION_FAILED; + } + + ib_table->release(); + DBUG_RETURN(ret_err); + } + } + + m_prebuilt = row_create_prebuilt(ib_table, table->s->reclength); + + m_prebuilt->default_rec = table->s->default_values; + ut_ad(m_prebuilt->default_rec); + + m_prebuilt->m_mysql_table = table; + + /* Looks like MySQL-3.23 sometimes has primary key number != 0 */ + m_primary_key = table->s->primary_key; + + key_used_on_scan = m_primary_key; + + if (ib_table->n_v_cols) { + dict_sys.lock(SRW_LOCK_CALL); + if (ib_table->vc_templ == NULL) { + ib_table->vc_templ = UT_NEW_NOKEY(dict_vcol_templ_t()); + innobase_build_v_templ( + table, ib_table, ib_table->vc_templ, NULL, + true); + } + + dict_sys.unlock(); + } + + if (!check_index_consistency(table, ib_table)) { + sql_print_error("InnoDB indexes are inconsistent with what " + "defined in .frm for table %s", + name); + } + + /* Allocate a buffer for a 'row reference'. A row reference is + a string of bytes of length ref_length which uniquely specifies + a row in our table. Note that MySQL may also compare two row + references for equality by doing a simple memcmp on the strings + of length ref_length! */ + if (!(m_prebuilt->clust_index_was_generated + = dict_index_is_auto_gen_clust(ib_table->indexes.start))) { + if (m_primary_key >= MAX_KEY) { + ib_table->dict_frm_mismatch = DICT_FRM_NO_PK; + + /* This mismatch could cause further problems + if not attended, bring this to the user's attention + by printing a warning in addition to log a message + in the errorlog */ + + ib_push_frm_error(thd, ib_table, table, 0, true); + + /* If m_primary_key >= MAX_KEY, its (m_primary_key) + value could be out of bound if continue to index + into key_info[] array. Find InnoDB primary index, + and assign its key_length to ref_length. + In addition, since MySQL indexes are sorted starting + with primary index, unique index etc., initialize + ref_length to the first index key length in + case we fail to find InnoDB cluster index. + + Please note, this will not resolve the primary + index mismatch problem, other side effects are + possible if users continue to use the table. + However, we allow this table to be opened so + that user can adopt necessary measures for the + mismatch while still being accessible to the table + date. */ + if (!table->key_info) { + ut_ad(!table->s->keys); + ref_length = 0; + } else { + ref_length = table->key_info[0].key_length; + } + + /* Find corresponding cluster index + key length in MySQL's key_info[] array */ + for (uint i = 0; i < table->s->keys; i++) { + dict_index_t* index; + index = innobase_get_index(i); + if (dict_index_is_clust(index)) { + ref_length = + table->key_info[i].key_length; + } + } + } else { + /* MySQL allocates the buffer for ref. + key_info->key_length includes space for all key + columns + one byte for each column that may be + NULL. ref_length must be as exact as possible to + save space, because all row reference buffers are + allocated based on ref_length. */ + + ref_length = table->key_info[m_primary_key].key_length; + } + } else { + if (m_primary_key != MAX_KEY) { + + ib_table->dict_frm_mismatch = DICT_NO_PK_FRM_HAS; + + /* This mismatch could cause further problems + if not attended, bring this to the user attention + by printing a warning in addition to log a message + in the errorlog */ + ib_push_frm_error(thd, ib_table, table, 0, true); + } + + ref_length = DATA_ROW_ID_LEN; + + /* If we automatically created the clustered index, then + MySQL does not know about it, and MySQL must NOT be aware + of the index used on scan, to make it avoid checking if we + update the column of the index. That is why we assert below + that key_used_on_scan is the undefined value MAX_KEY. + The column is the row id in the automatical generation case, + and it will never be updated anyway. */ + + if (key_used_on_scan != MAX_KEY) { + sql_print_warning( + "Table %s key_used_on_scan is %u even " + "though there is no primary key inside " + "InnoDB.", name, key_used_on_scan); + } + } + + /* Index block size in InnoDB: used by MySQL in query optimization */ + stats.block_size = static_cast<uint>(srv_page_size); + + const my_bool for_vc_purge = THDVAR(thd, background_thread); + + if (for_vc_purge || !m_prebuilt->table + || m_prebuilt->table->is_temporary() + || m_prebuilt->table->persistent_autoinc + || !m_prebuilt->table->is_readable()) { + } else if (const Field* ai = table->found_next_number_field) { + initialize_auto_increment(m_prebuilt->table, ai); + } + + /* Set plugin parser for fulltext index */ + for (uint i = 0; i < table->s->keys; i++) { + if (table->key_info[i].flags & HA_USES_PARSER) { + dict_index_t* index = innobase_get_index(i); + plugin_ref parser = table->key_info[i].parser; + + ut_ad(index->type & DICT_FTS); + index->parser = + static_cast<st_mysql_ftparser *>( + plugin_decl(parser)->info); + + DBUG_EXECUTE_IF("fts_instrument_use_default_parser", + index->parser = &fts_default_parser;); + } + } + + ut_ad(!m_prebuilt->table + || table->versioned() == m_prebuilt->table->versioned()); + + if (!for_vc_purge) { + info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST + | HA_STATUS_OPEN); + } + + DBUG_RETURN(0); +} + +/** Convert MySQL column number to dict_table_t::cols[] offset. +@param[in] field non-virtual column +@return column number relative to dict_table_t::cols[] */ +unsigned +innodb_col_no(const Field* field) +{ + ut_ad(!innobase_is_s_fld(field)); + const TABLE* table = field->table; + unsigned col_no = 0; + ut_ad(field == table->field[field->field_index]); + for (unsigned i = 0; i < field->field_index; i++) { + if (table->field[i]->stored_in_db()) { + col_no++; + } + } + return(col_no); +} + +/** Opens dictionary table object using table name. For partition, we need to +try alternative lower/upper case names to support moving data files across +platforms. +@param[in] table_name name of the table/partition +@param[in] norm_name normalized name of the table/partition +@param[in] is_partition if this is a partition of a table +@param[in] ignore_err error to ignore for loading dictionary object +@return dictionary table object or NULL if not found */ +dict_table_t* +ha_innobase::open_dict_table( + const char* +#ifdef _WIN32 + table_name +#endif + , + const char* norm_name, + bool is_partition, + dict_err_ignore_t ignore_err) +{ + DBUG_ENTER("ha_innobase::open_dict_table"); + /* FIXME: try_drop_aborted */ + dict_table_t* ib_table = dict_table_open_on_name(norm_name, false, + ignore_err); + + if (NULL == ib_table && is_partition) { + /* MySQL partition engine hard codes the file name + separator as "#P#". The text case is fixed even if + lower_case_table_names is set to 1 or 2. This is true + for sub-partition names as well. InnoDB always + normalises file names to lower case on Windows, this + can potentially cause problems when copying/moving + tables between platforms. + + 1) If boot against an installation from Windows + platform, then its partition table name could + be in lower case in system tables. So we will + need to check lower case name when load table. + + 2) If we boot an installation from other case + sensitive platform in Windows, we might need to + check the existence of table name without lower + case in the system table. */ + if (lower_case_table_names == 1) { + char par_case_name[FN_REFLEN]; + +#ifndef _WIN32 + /* Check for the table using lower + case name, including the partition + separator "P" */ + strcpy(par_case_name, norm_name); + innobase_casedn_str(par_case_name); +#else + /* On Windows platfrom, check + whether there exists table name in + system table whose name is + not being normalized to lower case */ + normalize_table_name_c_low( + par_case_name, table_name, false); +#endif + /* FIXME: try_drop_aborted */ + ib_table = dict_table_open_on_name( + par_case_name, false, ignore_err); + } + + if (ib_table != NULL) { +#ifndef _WIN32 + sql_print_warning("Partition table %s opened" + " after converting to lower" + " case. The table may have" + " been moved from a case" + " in-sensitive file system." + " Please recreate table in" + " the current file system\n", + norm_name); +#else + sql_print_warning("Partition table %s opened" + " after skipping the step to" + " lower case the table name." + " The table may have been" + " moved from a case sensitive" + " file system. Please" + " recreate table in the" + " current file system\n", + norm_name); +#endif + } + } + + DBUG_RETURN(ib_table); +} + +handler* +ha_innobase::clone( +/*===============*/ + const char* name, /*!< in: table name */ + MEM_ROOT* mem_root) /*!< in: memory context */ +{ + DBUG_ENTER("ha_innobase::clone"); + + ha_innobase* new_handler = static_cast<ha_innobase*>( + handler::clone(m_prebuilt->table->name.m_name, mem_root)); + + if (new_handler != NULL) { + DBUG_ASSERT(new_handler->m_prebuilt != NULL); + + new_handler->m_prebuilt->select_lock_type + = m_prebuilt->select_lock_type; + } + + DBUG_RETURN(new_handler); +} + + +uint +ha_innobase::max_supported_key_part_length() const +/*==============================================*/ +{ + /* A table format specific index column length check will be performed + at ha_innobase::add_index() and row_create_index_for_mysql() */ + return(REC_VERSION_56_MAX_INDEX_COL_LEN); +} + +/******************************************************************//** +Closes a handle to an InnoDB table. +@return 0 */ + +int +ha_innobase::close() +/*================*/ +{ + DBUG_ENTER("ha_innobase::close"); + + row_prebuilt_free(m_prebuilt); + + if (m_upd_buf != NULL) { + ut_ad(m_upd_buf_size != 0); + my_free(m_upd_buf); + m_upd_buf = NULL; + m_upd_buf_size = 0; + } + + DBUG_RETURN(0); +} + +/* The following accessor functions should really be inside MySQL code! */ + +#ifdef WITH_WSREP +ulint +wsrep_innobase_mysql_sort( + /* out: str contains sort string */ + int mysql_type, /* in: MySQL type */ + uint charset_number, /* in: number of the charset */ + unsigned char* str, /* in: data field */ + ulint str_length, /* in: data field length, + not UNIV_SQL_NULL */ + ulint buf_length) /* in: total str buffer length */ + +{ + CHARSET_INFO* charset; + enum_field_types mysql_tp; + ulint ret_length = str_length; + + DBUG_ASSERT(str_length != UNIV_SQL_NULL); + + mysql_tp = (enum_field_types) mysql_type; + + switch (mysql_tp) { + + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_VARCHAR: + { + uchar tmp_str[REC_VERSION_56_MAX_INDEX_COL_LEN] = {'\0'}; + ulint tmp_length = REC_VERSION_56_MAX_INDEX_COL_LEN; + + /* Use the charset number to pick the right charset struct for + the comparison. Since the MySQL function get_charset may be + slow before Bar removes the mutex operation there, we first + look at 2 common charsets directly. */ + + if (charset_number == default_charset_info->number) { + charset = default_charset_info; + } else if (charset_number == my_charset_latin1.number) { + charset = &my_charset_latin1; + } else { + charset = get_charset(charset_number, MYF(MY_WME)); + + if (charset == NULL) { + sql_print_error("InnoDB needs charset %lu for doing " + "a comparison, but MariaDB cannot " + "find that charset.", + (ulong) charset_number); + ut_a(0); + } + } + + ut_a(str_length <= tmp_length); + memcpy(tmp_str, str, str_length); + + tmp_length = charset->strnxfrm(str, str_length, + uint(str_length), tmp_str, + tmp_length, 0); + DBUG_ASSERT(tmp_length <= str_length); + if (wsrep_protocol_version < 3) { + tmp_length = charset->strnxfrm( + str, str_length, + uint(str_length), tmp_str, tmp_length, 0); + DBUG_ASSERT(tmp_length <= str_length); + } else { + /* strnxfrm will expand the destination string, + protocols < 3 truncated the sorted sring + protocols >= 3 gets full sorted sring + */ + tmp_length = charset->strnxfrm( + str, buf_length, + uint(str_length), tmp_str, str_length, 0); + DBUG_ASSERT(tmp_length <= buf_length); + ret_length = tmp_length; + } + + break; + } + case MYSQL_TYPE_DECIMAL : + case MYSQL_TYPE_TINY : + case MYSQL_TYPE_SHORT : + case MYSQL_TYPE_LONG : + case MYSQL_TYPE_FLOAT : + case MYSQL_TYPE_DOUBLE : + case MYSQL_TYPE_NULL : + case MYSQL_TYPE_TIMESTAMP : + case MYSQL_TYPE_LONGLONG : + case MYSQL_TYPE_INT24 : + case MYSQL_TYPE_DATE : + case MYSQL_TYPE_TIME : + case MYSQL_TYPE_DATETIME : + case MYSQL_TYPE_YEAR : + case MYSQL_TYPE_NEWDATE : + case MYSQL_TYPE_NEWDECIMAL : + case MYSQL_TYPE_ENUM : + case MYSQL_TYPE_SET : + case MYSQL_TYPE_GEOMETRY : + break; + default: + break; + } + + return ret_length; +} +#endif /* WITH_WSREP */ + +/******************************************************************//** +compare two character string according to their charset. */ +int +innobase_fts_text_cmp( +/*==================*/ + const void* cs, /*!< in: Character set */ + const void* p1, /*!< in: key */ + const void* p2) /*!< in: node */ +{ + const CHARSET_INFO* charset = (const CHARSET_INFO*) cs; + const fts_string_t* s1 = (const fts_string_t*) p1; + const fts_string_t* s2 = (const fts_string_t*) p2; + + return(ha_compare_word(charset, + s1->f_str, static_cast<uint>(s1->f_len), + s2->f_str, static_cast<uint>(s2->f_len))); +} + +/******************************************************************//** +compare two character string case insensitively according to their charset. */ +int +innobase_fts_text_case_cmp( +/*=======================*/ + const void* cs, /*!< in: Character set */ + const void* p1, /*!< in: key */ + const void* p2) /*!< in: node */ +{ + const CHARSET_INFO* charset = (const CHARSET_INFO*) cs; + const fts_string_t* s1 = (const fts_string_t*) p1; + const fts_string_t* s2 = (const fts_string_t*) p2; + ulint newlen; + + my_casedn_str(charset, (char*) s2->f_str); + + newlen = strlen((const char*) s2->f_str); + + return(ha_compare_word(charset, + s1->f_str, static_cast<uint>(s1->f_len), + s2->f_str, static_cast<uint>(newlen))); +} + +/******************************************************************//** +Get the first character's code position for FTS index partition. */ +ulint +innobase_strnxfrm( +/*==============*/ + const CHARSET_INFO* + cs, /*!< in: Character set */ + const uchar* str, /*!< in: string */ + const ulint len) /*!< in: string length */ +{ + uchar mystr[2]; + ulint value; + + if (!str || len == 0) { + return(0); + } + + cs->strnxfrm((uchar*) mystr, 2, str, len); + + value = mach_read_from_2(mystr); + + if (value > 255) { + value = value / 256; + } + + return(value); +} + +/******************************************************************//** +compare two character string according to their charset. */ +int +innobase_fts_text_cmp_prefix( +/*=========================*/ + const void* cs, /*!< in: Character set */ + const void* p1, /*!< in: prefix key */ + const void* p2) /*!< in: value to compare */ +{ + const CHARSET_INFO* charset = (const CHARSET_INFO*) cs; + const fts_string_t* s1 = (const fts_string_t*) p1; + const fts_string_t* s2 = (const fts_string_t*) p2; + int result; + + result = ha_compare_word_prefix(charset, + s2->f_str, static_cast<uint>(s2->f_len), + s1->f_str, static_cast<uint>(s1->f_len)); + + /* We switched s1, s2 position in the above call. So we need + to negate the result */ + return(-result); +} + +/******************************************************************//** +Makes all characters in a string lower case. */ +size_t +innobase_fts_casedn_str( +/*====================*/ + CHARSET_INFO* cs, /*!< in: Character set */ + char* src, /*!< in: string to put in lower case */ + size_t src_len,/*!< in: input string length */ + char* dst, /*!< in: buffer for result string */ + size_t dst_len)/*!< in: buffer size */ +{ + if (cs->casedn_multiply() == 1) { + memcpy(dst, src, src_len); + dst[src_len] = 0; + my_casedn_str(cs, dst); + + return(strlen(dst)); + } else { + return(cs->casedn(src, src_len, dst, dst_len)); + } +} + +#define true_word_char(c, ch) ((c) & (_MY_U | _MY_L | _MY_NMR) || (ch) == '_') + +#define misc_word_char(X) 0 + +/*************************************************************//** +Get the next token from the given string and store it in *token. +It is mostly copied from MyISAM's doc parsing function ft_simple_get_word() +@return length of string processed */ +ulint +innobase_mysql_fts_get_token( +/*=========================*/ + CHARSET_INFO* cs, /*!< in: Character set */ + const byte* start, /*!< in: start of text */ + const byte* end, /*!< in: one character past end of + text */ + fts_string_t* token) /*!< out: token's text */ +{ + int mbl; + const uchar* doc = start; + + ut_a(cs); + + token->f_n_char = token->f_len = 0; + token->f_str = NULL; + + for (;;) { + + if (doc >= end) { + return ulint(doc - start); + } + + int ctype; + + mbl = cs->ctype(&ctype, doc, (const uchar*) end); + + if (true_word_char(ctype, *doc)) { + break; + } + + doc += mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1); + } + + ulint mwc = 0; + ulint length = 0; + bool reset_token_str = false; +reset: + token->f_str = const_cast<byte*>(doc); + + while (doc < end) { + + int ctype; + + mbl = cs->ctype(&ctype, (uchar*) doc, (uchar*) end); + if (true_word_char(ctype, *doc)) { + mwc = 0; + } else if (*doc == '\'' && length == 1) { + /* Could be apostrophe */ + reset_token_str = true; + } else if (!misc_word_char(*doc) || mwc) { + break; + } else { + ++mwc; + } + + ++length; + + doc += mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1); + if (reset_token_str) { + /* Reset the token if the single character + followed by apostrophe */ + mwc = 0; + length = 0; + reset_token_str = false; + goto reset; + } + } + + token->f_len = (uint) (doc - token->f_str) - mwc; + token->f_n_char = length; + + return ulint(doc - start); +} + +/** Converts a MySQL type to an InnoDB type. Note that this function returns +the 'mtype' of InnoDB. InnoDB differentiates between MySQL's old <= 4.1 +VARCHAR and the new true VARCHAR in >= 5.0.3 by the 'prtype'. +@param[out] unsigned_flag DATA_UNSIGNED if an 'unsigned type'; at least +ENUM and SET, and unsigned integer types are 'unsigned types' +@param[in] f MySQL Field +@return DATA_BINARY, DATA_VARCHAR, ... */ +uint8_t +get_innobase_type_from_mysql_type(unsigned *unsigned_flag, const Field *field) +{ + /* The following asserts try to check that the MySQL type code fits in + 8 bits: this is used in ibuf and also when DATA_NOT_NULL is ORed to + the type */ + + static_assert(MYSQL_TYPE_STRING < 256, "compatibility"); + static_assert(MYSQL_TYPE_VAR_STRING < 256, "compatibility"); + static_assert(MYSQL_TYPE_DOUBLE < 256, "compatibility"); + static_assert(MYSQL_TYPE_FLOAT < 256, "compatibility"); + static_assert(MYSQL_TYPE_DECIMAL < 256, "compatibility"); + + if (field->flags & UNSIGNED_FLAG) { + + *unsigned_flag = DATA_UNSIGNED; + } else { + *unsigned_flag = 0; + } + + if (field->real_type() == MYSQL_TYPE_ENUM + || field->real_type() == MYSQL_TYPE_SET) { + + /* MySQL has field->type() a string type for these, but the + data is actually internally stored as an unsigned integer + code! */ + + *unsigned_flag = DATA_UNSIGNED; /* MySQL has its own unsigned + flag set to zero, even though + internally this is an unsigned + integer type */ + return(DATA_INT); + } + + switch (field->type()) { + /* NOTE that we only allow string types in DATA_MYSQL and + DATA_VARMYSQL */ + case MYSQL_TYPE_VAR_STRING: /* old <= 4.1 VARCHAR */ + case MYSQL_TYPE_VARCHAR: /* new >= 5.0.3 true VARCHAR */ + if (field->binary()) { + return(DATA_BINARY); + } else if (field->charset() == &my_charset_latin1) { + return(DATA_VARCHAR); + } else { + return(DATA_VARMYSQL); + } + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_STRING: + if (field->binary() || field->key_type() == HA_KEYTYPE_BINARY) { + return(DATA_FIXBINARY); + } else if (field->charset() == &my_charset_latin1) { + return(DATA_CHAR); + } else { + return(DATA_MYSQL); + } + case MYSQL_TYPE_NEWDECIMAL: + return(DATA_FIXBINARY); + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + return(DATA_INT); + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + if (field->key_type() == HA_KEYTYPE_BINARY) { + return(DATA_FIXBINARY); + } else { + return(DATA_INT); + } + case MYSQL_TYPE_FLOAT: + return(DATA_FLOAT); + case MYSQL_TYPE_DOUBLE: + return(DATA_DOUBLE); + case MYSQL_TYPE_DECIMAL: + return(DATA_DECIMAL); + case MYSQL_TYPE_GEOMETRY: + return(DATA_GEOMETRY); + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + return(DATA_BLOB); + case MYSQL_TYPE_NULL: + /* MySQL currently accepts "NULL" datatype, but will + reject such datatype in the next release. We will cope + with it and not trigger assertion failure in 5.1 */ + break; + default: + ut_error; + } + + return(0); +} + +/*******************************************************************//** +Reads an unsigned integer value < 64k from 2 bytes, in the little-endian +storage format. +@return value */ +static inline +uint +innobase_read_from_2_little_endian( +/*===============================*/ + const uchar* buf) /*!< in: from where to read */ +{ + return((uint) ((ulint)(buf[0]) + 256 * ((ulint)(buf[1])))); +} + +#ifdef WITH_WSREP +/*******************************************************************//** +Stores a key value for a row to a buffer. +@return key value length as stored in buff */ +static +uint16_t +wsrep_store_key_val_for_row( +/*=========================*/ + THD* thd, + TABLE* table, + uint keynr, /*!< in: key number */ + char* buff, /*!< in/out: buffer for the key value (in MySQL + format) */ + uint buff_len,/*!< in: buffer length */ + const uchar* record, + bool* key_is_null)/*!< out: full key was null */ +{ + KEY* key_info = table->key_info + keynr; + KEY_PART_INFO* key_part = key_info->key_part; + KEY_PART_INFO* end = key_part + key_info->user_defined_key_parts; + char* buff_start = buff; + enum_field_types mysql_type; + Field* field; + ulint buff_space = buff_len; + + DBUG_ENTER("wsrep_store_key_val_for_row"); + + memset(buff, 0, buff_len); + *key_is_null = true; + + for (; key_part != end; key_part++) { + uchar sorted[REC_VERSION_56_MAX_INDEX_COL_LEN] = {'\0'}; + bool part_is_null = false; + + if (key_part->null_bit) { + if (buff_space > 0) { + if (record[key_part->null_offset] + & key_part->null_bit) { + *buff = 1; + part_is_null = true; + } else { + *buff = 0; + } + buff++; + buff_space--; + } else { + fprintf (stderr, "WSREP: key truncated: %s\n", + wsrep_thd_query(thd)); + } + } + if (!part_is_null) *key_is_null = false; + + field = key_part->field; + mysql_type = field->type(); + + if (mysql_type == MYSQL_TYPE_VARCHAR) { + /* >= 5.0.3 true VARCHAR */ + ulint lenlen; + ulint len; + const byte* data; + ulint key_len; + ulint true_len; + const CHARSET_INFO* cs; + int error=0; + + key_len = key_part->length; + + if (part_is_null) { + true_len = key_len + 2; + if (true_len > buff_space) { + fprintf (stderr, + "WSREP: key truncated: %s\n", + wsrep_thd_query(thd)); + true_len = buff_space; + } + buff += true_len; + buff_space -= true_len; + continue; + } + cs = field->charset(); + + lenlen = (ulint) + (((Field_varstring*)field)->length_bytes); + + data = row_mysql_read_true_varchar(&len, + (byte*) (record + + (ulint)get_field_offset(table, field)), + lenlen); + + true_len = len; + + /* For multi byte character sets we need to calculate + the true length of the key */ + + if (len > 0 && cs->mbmaxlen > 1) { + true_len = (ulint) my_well_formed_length(cs, + (const char *) data, + (const char *) data + len, + (uint) (key_len / + cs->mbmaxlen), + &error); + } + + /* In a column prefix index, we may need to truncate + the stored value: */ + if (true_len > key_len) { + true_len = key_len; + } + /* cannot exceed max column lenght either, we may need to truncate + the stored value: */ + if (true_len > sizeof(sorted)) { + true_len = sizeof(sorted); + } + + memcpy(sorted, data, true_len); + true_len = wsrep_innobase_mysql_sort( + mysql_type, cs->number, sorted, true_len, + REC_VERSION_56_MAX_INDEX_COL_LEN); + if (wsrep_protocol_version > 1) { + /* Note that we always reserve the maximum possible + length of the true VARCHAR in the key value, though + only len first bytes after the 2 length bytes contain + actual data. The rest of the space was reset to zero + in the bzero() call above. */ + if (true_len > buff_space) { + WSREP_DEBUG ( + "write set key truncated for: %s\n", + wsrep_thd_query(thd)); + true_len = buff_space; + } + memcpy(buff, sorted, true_len); + buff += true_len; + buff_space -= true_len; + } else { + buff += key_len; + } + } else if (mysql_type == MYSQL_TYPE_TINY_BLOB + || mysql_type == MYSQL_TYPE_MEDIUM_BLOB + || mysql_type == MYSQL_TYPE_BLOB + || mysql_type == MYSQL_TYPE_LONG_BLOB + /* MYSQL_TYPE_GEOMETRY data is treated + as BLOB data in innodb. */ + || mysql_type == MYSQL_TYPE_GEOMETRY) { + + const CHARSET_INFO* cs; + ulint key_len; + ulint true_len; + int error=0; + ulint blob_len; + const byte* blob_data; + + ut_a(key_part->key_part_flag & HA_PART_KEY_SEG); + + key_len = key_part->length; + + if (part_is_null) { + true_len = key_len + 2; + if (true_len > buff_space) { + fprintf (stderr, + "WSREP: key truncated: %s\n", + wsrep_thd_query(thd)); + true_len = buff_space; + } + buff += true_len; + buff_space -= true_len; + + continue; + } + + cs = field->charset(); + + blob_data = row_mysql_read_blob_ref(&blob_len, + (byte*) (record + + (ulint)get_field_offset(table, field)), + (ulint) field->pack_length()); + + true_len = blob_len; + + ut_a(get_field_offset(table, field) + == key_part->offset); + + /* For multi byte character sets we need to calculate + the true length of the key */ + + if (blob_len > 0 && cs->mbmaxlen > 1) { + true_len = (ulint) my_well_formed_length(cs, + (const char *) blob_data, + (const char *) blob_data + + blob_len, + (uint) (key_len / + cs->mbmaxlen), + &error); + } + + /* All indexes on BLOB and TEXT are column prefix + indexes, and we may need to truncate the data to be + stored in the key value: */ + + if (true_len > key_len) { + true_len = key_len; + } + + memcpy(sorted, blob_data, true_len); + true_len = wsrep_innobase_mysql_sort( + mysql_type, cs->number, sorted, true_len, + REC_VERSION_56_MAX_INDEX_COL_LEN); + + + /* Note that we always reserve the maximum possible + length of the BLOB prefix in the key value. */ + if (wsrep_protocol_version > 1) { + if (true_len > buff_space) { + fprintf (stderr, + "WSREP: key truncated: %s\n", + wsrep_thd_query(thd)); + true_len = buff_space; + } + buff += true_len; + buff_space -= true_len; + } else { + buff += key_len; + } + memcpy(buff, sorted, true_len); + } else { + /* Here we handle all other data types except the + true VARCHAR, BLOB and TEXT. Note that the column + value we store may be also in a column prefix + index. */ + + const CHARSET_INFO* cs = NULL; + ulint true_len; + ulint key_len; + const uchar* src_start; + int error=0; + enum_field_types real_type; + + key_len = key_part->length; + + if (part_is_null) { + true_len = key_len; + if (true_len > buff_space) { + fprintf (stderr, + "WSREP: key truncated: %s\n", + wsrep_thd_query(thd)); + true_len = buff_space; + } + buff += true_len; + buff_space -= true_len; + + continue; + } + + src_start = record + key_part->offset; + real_type = field->real_type(); + true_len = key_len; + + /* Character set for the field is defined only + to fields whose type is string and real field + type is not enum or set. For these fields check + if character set is multi byte. */ + + if (real_type != MYSQL_TYPE_ENUM + && real_type != MYSQL_TYPE_SET + && ( mysql_type == MYSQL_TYPE_VAR_STRING + || mysql_type == MYSQL_TYPE_STRING)) { + + cs = field->charset(); + + /* For multi byte character sets we need to + calculate the true length of the key */ + + if (key_len > 0 && cs->mbmaxlen > 1) { + + true_len = (ulint) + my_well_formed_length(cs, + (const char *)src_start, + (const char *)src_start + + key_len, + (uint) (key_len / + cs->mbmaxlen), + &error); + } + memcpy(sorted, src_start, true_len); + true_len = wsrep_innobase_mysql_sort( + mysql_type, cs->number, sorted, true_len, + REC_VERSION_56_MAX_INDEX_COL_LEN); + + if (true_len > buff_space) { + fprintf (stderr, + "WSREP: key truncated: %s\n", + wsrep_thd_query(thd)); + true_len = buff_space; + } + memcpy(buff, sorted, true_len); + } else { + memcpy(buff, src_start, true_len); + } + buff += true_len; + buff_space -= true_len; + } + } + + ut_a(buff <= buff_start + buff_len); + + DBUG_RETURN(static_cast<uint16_t>(buff - buff_start)); +} +#endif /* WITH_WSREP */ +/**************************************************************//** +Determines if a field is needed in a m_prebuilt struct 'template'. +@return field to use, or NULL if the field is not needed */ +static +const Field* +build_template_needs_field( +/*=======================*/ + bool index_contains, /*!< in: + dict_index_t::contains_col_or_prefix( + i) */ + bool read_just_key, /*!< in: TRUE when MySQL calls + ha_innobase::extra with the + argument HA_EXTRA_KEYREAD; it is enough + to read just columns defined in + the index (i.e., no read of the + clustered index record necessary) */ + bool fetch_all_in_key, + /*!< in: true=fetch all fields in + the index */ + bool fetch_primary_key_cols, + /*!< in: true=fetch the + primary key columns */ + dict_index_t* index, /*!< in: InnoDB index to use */ + const TABLE* table, /*!< in: MySQL table object */ + ulint i, /*!< in: field index in InnoDB table */ + ulint num_v) /*!< in: num virtual column so far */ +{ + const Field* field = table->field[i]; + + if (!field->stored_in_db() + && ha_innobase::omits_virtual_cols(*table->s)) { + return NULL; + } + + if (!index_contains) { + if (read_just_key) { + /* If this is a 'key read', we do not need + columns that are not in the key */ + + return(NULL); + } + } else if (fetch_all_in_key) { + /* This field is needed in the query */ + + return(field); + } + + if (bitmap_is_set(table->read_set, static_cast<uint>(i)) + || bitmap_is_set(table->write_set, static_cast<uint>(i))) { + /* This field is needed in the query */ + + return(field); + } + + ut_ad(i >= num_v); + if (fetch_primary_key_cols + && dict_table_col_in_clustered_key(index->table, i - num_v)) { + /* This field is needed in the query */ + return(field); + } + + /* This field is not needed in the query, skip it */ + + return(NULL); +} + +/**************************************************************//** +Determines if a field is needed in a m_prebuilt struct 'template'. +@return whether the field is needed for index condition pushdown */ +inline +bool +build_template_needs_field_in_icp( +/*==============================*/ + const dict_index_t* index, /*!< in: InnoDB index */ + const row_prebuilt_t* prebuilt,/*!< in: row fetch template */ + bool contains,/*!< in: whether the index contains + column i */ + ulint i, /*!< in: column number */ + bool is_virtual) + /*!< in: a virtual column or not */ +{ + ut_ad(contains == index->contains_col_or_prefix(i, is_virtual)); + + return(index == prebuilt->index + ? contains + : prebuilt->index->contains_col_or_prefix(i, is_virtual)); +} + +/**************************************************************//** +Adds a field to a m_prebuilt struct 'template'. +@return the field template */ +static +mysql_row_templ_t* +build_template_field( +/*=================*/ + row_prebuilt_t* prebuilt, /*!< in/out: template */ + dict_index_t* clust_index, /*!< in: InnoDB clustered index */ + dict_index_t* index, /*!< in: InnoDB index to use */ + TABLE* table, /*!< in: MySQL table object */ + const Field* field, /*!< in: field in MySQL table */ + ulint i, /*!< in: field index in InnoDB table */ + ulint v_no) /*!< in: field index for virtual col */ +{ + mysql_row_templ_t* templ; + const dict_col_t* col; + + ut_ad(clust_index->table == index->table); + + templ = prebuilt->mysql_template + prebuilt->n_template++; + MEM_UNDEFINED(templ, sizeof *templ); + templ->rec_field_is_prefix = FALSE; + templ->rec_prefix_field_no = ULINT_UNDEFINED; + templ->is_virtual = !field->stored_in_db(); + + if (!templ->is_virtual) { + templ->col_no = i; + col = dict_table_get_nth_col(index->table, i); + templ->clust_rec_field_no = dict_col_get_clust_pos( + col, clust_index); + /* If clustered index record field is not found, lets print out + field names and all the rest to understand why field is not found. */ + if (templ->clust_rec_field_no == ULINT_UNDEFINED) { + const char* tb_col_name = dict_table_get_col_name(clust_index->table, i); + dict_field_t* field=NULL; + size_t size = 0; + + for(ulint j=0; j < clust_index->n_user_defined_cols; j++) { + dict_field_t* ifield = &(clust_index->fields[j]); + if (ifield && !memcmp(tb_col_name, ifield->name, + strlen(tb_col_name))) { + field = ifield; + break; + } + } + + ib::info() << "Looking for field " << i << " name " + << (tb_col_name ? tb_col_name : "NULL") + << " from table " << clust_index->table->name; + + + for(ulint j=0; j < clust_index->n_user_defined_cols; j++) { + dict_field_t* ifield = &(clust_index->fields[j]); + ib::info() << "InnoDB Table " + << clust_index->table->name + << "field " << j << " name " + << (ifield ? ifield->name() : "NULL"); + } + + for(ulint j=0; j < table->s->stored_fields; j++) { + ib::info() << "MySQL table " + << table->s->table_name.str + << " field " << j << " name " + << table->field[j]->field_name.str; + } + + ib::fatal() << "Clustered record field for column " << i + << " not found table n_user_defined " + << clust_index->n_user_defined_cols + << " index n_user_defined " + << clust_index->table->n_cols - DATA_N_SYS_COLS + << " InnoDB table " + << clust_index->table->name + << " field name " + << (field ? field->name() : "NULL") + << " MySQL table " + << table->s->table_name.str + << " field name " + << (tb_col_name ? tb_col_name : "NULL") + << " n_fields " + << table->s->stored_fields + << " query " + << innobase_get_stmt_unsafe(current_thd, &size); + } + + if (dict_index_is_clust(index)) { + templ->rec_field_no = templ->clust_rec_field_no; + } else { + /* If we're in a secondary index, keep track + * of the original index position even if this + * is just a prefix index; we will use this + * later to avoid a cluster index lookup in + * some cases.*/ + + templ->rec_field_no = dict_index_get_nth_col_pos(index, i, + &templ->rec_prefix_field_no); + } + } else { + DBUG_ASSERT(!ha_innobase::omits_virtual_cols(*table->s)); + col = &dict_table_get_nth_v_col(index->table, v_no)->m_col; + templ->clust_rec_field_no = v_no; + + if (dict_index_is_clust(index)) { + templ->rec_field_no = templ->clust_rec_field_no; + } else { + templ->rec_field_no + = dict_index_get_nth_col_or_prefix_pos( + index, v_no, FALSE, true, + &templ->rec_prefix_field_no); + } + templ->icp_rec_field_no = ULINT_UNDEFINED; + } + + if (field->real_maybe_null()) { + templ->mysql_null_byte_offset = + field->null_offset(); + + templ->mysql_null_bit_mask = (ulint) field->null_bit; + } else { + templ->mysql_null_bit_mask = 0; + } + + + templ->mysql_col_offset = (ulint) get_field_offset(table, field); + templ->mysql_col_len = (ulint) field->pack_length(); + templ->type = col->mtype; + templ->mysql_type = (ulint) field->type(); + + if (templ->mysql_type == DATA_MYSQL_TRUE_VARCHAR) { + templ->mysql_length_bytes = (ulint) + (((Field_varstring*) field)->length_bytes); + } else { + templ->mysql_length_bytes = 0; + } + + templ->charset = dtype_get_charset_coll(col->prtype); + templ->mbminlen = dict_col_get_mbminlen(col); + templ->mbmaxlen = dict_col_get_mbmaxlen(col); + templ->is_unsigned = col->prtype & DATA_UNSIGNED; + + if (!dict_index_is_clust(index) + && templ->rec_field_no == ULINT_UNDEFINED) { + prebuilt->need_to_access_clustered = TRUE; + + if (templ->rec_prefix_field_no != ULINT_UNDEFINED) { + dict_field_t* field = dict_index_get_nth_field( + index, + templ->rec_prefix_field_no); + templ->rec_field_is_prefix = (field->prefix_len != 0); + } + } + + /* For spatial index, we need to access cluster index. */ + if (dict_index_is_spatial(index)) { + prebuilt->need_to_access_clustered = TRUE; + } + + if (prebuilt->mysql_prefix_len < templ->mysql_col_offset + + templ->mysql_col_len) { + prebuilt->mysql_prefix_len = templ->mysql_col_offset + + templ->mysql_col_len; + } + + if (DATA_LARGE_MTYPE(templ->type)) { + prebuilt->templ_contains_blob = TRUE; + } + + return(templ); +} + +/**************************************************************//** +Builds a 'template' to the m_prebuilt struct. The template is used in fast +retrieval of just those column values MySQL needs in its processing. */ + +void +ha_innobase::build_template( +/*========================*/ + bool whole_row) /*!< in: true=ROW_MYSQL_WHOLE_ROW, + false=ROW_MYSQL_REC_FIELDS */ +{ + dict_index_t* index; + dict_index_t* clust_index; + ibool fetch_all_in_key = FALSE; + ibool fetch_primary_key_cols = FALSE; + + if (m_prebuilt->select_lock_type == LOCK_X || m_prebuilt->table->no_rollback()) { + /* We always retrieve the whole clustered index record if we + use exclusive row level locks, for example, if the read is + done in an UPDATE statement or if we are using a no rollback + table */ + + whole_row = true; + } else if (!whole_row) { + if (m_prebuilt->hint_need_to_fetch_extra_cols + == ROW_RETRIEVE_ALL_COLS) { + + /* We know we must at least fetch all columns in the + key, or all columns in the table */ + + if (m_prebuilt->read_just_key) { + /* MySQL has instructed us that it is enough + to fetch the columns in the key; looks like + MySQL can set this flag also when there is + only a prefix of the column in the key: in + that case we retrieve the whole column from + the clustered index */ + + fetch_all_in_key = TRUE; + } else { + whole_row = true; + } + } else if (m_prebuilt->hint_need_to_fetch_extra_cols + == ROW_RETRIEVE_PRIMARY_KEY) { + /* We must at least fetch all primary key cols. Note + that if the clustered index was internally generated + by InnoDB on the row id (no primary key was + defined), then row_search_mvcc() will always + retrieve the row id to a special buffer in the + m_prebuilt struct. */ + + fetch_primary_key_cols = TRUE; + } + } + + clust_index = dict_table_get_first_index(m_prebuilt->table); + + index = whole_row ? clust_index : m_prebuilt->index; + + m_prebuilt->versioned_write = table->versioned_write(VERS_TRX_ID); + m_prebuilt->need_to_access_clustered = (index == clust_index); + + if (m_prebuilt->in_fts_query) { + /* Do clustered index lookup to fetch the FTS_DOC_ID */ + m_prebuilt->need_to_access_clustered = true; + } + + /* Either m_prebuilt->index should be a secondary index, or it + should be the clustered index. */ + ut_ad(dict_index_is_clust(index) == (index == clust_index)); + + /* Below we check column by column if we need to access + the clustered index. */ + + if (pushed_rowid_filter && rowid_filter_is_active) { + fetch_primary_key_cols = TRUE; + m_prebuilt->pk_filter = this; + } else { + m_prebuilt->pk_filter = NULL; + } + + const bool skip_virtual = omits_virtual_cols(*table_share); + const ulint n_fields = table_share->fields; + + if (!m_prebuilt->mysql_template) { + m_prebuilt->mysql_template = (mysql_row_templ_t*) + ut_malloc_nokey(n_fields * sizeof(mysql_row_templ_t)); + } + + m_prebuilt->template_type = whole_row + ? ROW_MYSQL_WHOLE_ROW : ROW_MYSQL_REC_FIELDS; + m_prebuilt->null_bitmap_len = table->s->null_bytes + & dict_index_t::MAX_N_FIELDS; + + /* Prepare to build m_prebuilt->mysql_template[]. */ + m_prebuilt->templ_contains_blob = FALSE; + m_prebuilt->mysql_prefix_len = 0; + m_prebuilt->n_template = 0; + m_prebuilt->idx_cond_n_cols = 0; + + /* Note that in InnoDB, i is the column number in the table. + MySQL calls columns 'fields'. */ + + ulint num_v = 0; + + if (active_index != MAX_KEY + && active_index == pushed_idx_cond_keyno) { + m_prebuilt->idx_cond = this; + goto icp; + } else if (pushed_rowid_filter && rowid_filter_is_active) { +icp: + /* Push down an index condition or an end_range check. */ + for (ulint i = 0; i < n_fields; i++) { + const Field* field = table->field[i]; + const bool is_v = !field->stored_in_db(); + if (is_v && skip_virtual) { + num_v++; + continue; + } + bool index_contains = index->contains_col_or_prefix( + is_v ? num_v : i - num_v, is_v); + if (is_v && index_contains) { + m_prebuilt->n_template = 0; + num_v = 0; + goto no_icp; + } + + /* Test if an end_range or an index condition + refers to the field. Note that "index" and + "index_contains" may refer to the clustered index. + Index condition pushdown is relative to + m_prebuilt->index (the index that is being + looked up first). */ + + /* When join_read_always_key() invokes this + code via handler::ha_index_init() and + ha_innobase::index_init(), end_range is not + yet initialized. Because of that, we must + always check for index_contains, instead of + the subset + field->part_of_key.is_set(active_index) + which would be acceptable if end_range==NULL. */ + if (build_template_needs_field_in_icp( + index, m_prebuilt, index_contains, + is_v ? num_v : i - num_v, is_v)) { + if (!whole_row) { + field = build_template_needs_field( + index_contains, + m_prebuilt->read_just_key, + fetch_all_in_key, + fetch_primary_key_cols, + index, table, i, num_v); + if (!field) { + if (is_v) { + num_v++; + } + continue; + } + } + + ut_ad(!is_v); + + mysql_row_templ_t* templ= build_template_field( + m_prebuilt, clust_index, index, + table, field, i - num_v, 0); + + ut_ad(!templ->is_virtual); + + m_prebuilt->idx_cond_n_cols++; + ut_ad(m_prebuilt->idx_cond_n_cols + == m_prebuilt->n_template); + + if (index == m_prebuilt->index) { + templ->icp_rec_field_no + = templ->rec_field_no; + } else { + templ->icp_rec_field_no + = dict_index_get_nth_col_pos( + m_prebuilt->index, + i - num_v, + &templ->rec_prefix_field_no); + } + + if (dict_index_is_clust(m_prebuilt->index)) { + ut_ad(templ->icp_rec_field_no + != ULINT_UNDEFINED); + /* If the primary key includes + a column prefix, use it in + index condition pushdown, + because the condition is + evaluated before fetching any + off-page (externally stored) + columns. */ + if (templ->icp_rec_field_no + < m_prebuilt->index->n_uniq) { + /* This is a key column; + all set. */ + continue; + } + } else if (templ->icp_rec_field_no + != ULINT_UNDEFINED) { + continue; + } + + /* This is a column prefix index. + The column prefix can be used in + an end_range comparison. */ + + templ->icp_rec_field_no + = dict_index_get_nth_col_or_prefix_pos( + m_prebuilt->index, i - num_v, + true, false, + &templ->rec_prefix_field_no); + ut_ad(templ->icp_rec_field_no + != ULINT_UNDEFINED); + + /* Index condition pushdown can be used on + all columns of a secondary index, and on + the PRIMARY KEY columns. On the clustered + index, it must never be used on other than + PRIMARY KEY columns, because those columns + may be stored off-page, and we will not + fetch externally stored columns before + checking the index condition. */ + /* TODO: test the above with an assertion + like this. Note that index conditions are + currently pushed down as part of the + "optimizer phase" while end_range is done + as part of the execution phase. Therefore, + we were unable to use an accurate condition + for end_range in the "if" condition above, + and the following assertion would fail. + ut_ad(!dict_index_is_clust(m_prebuilt->index) + || templ->rec_field_no + < m_prebuilt->index->n_uniq); + */ + } + + if (is_v) { + num_v++; + } + } + + ut_ad(m_prebuilt->idx_cond_n_cols > 0); + ut_ad(m_prebuilt->idx_cond_n_cols == m_prebuilt->n_template); + + num_v = 0; + + /* Include the fields that are not needed in index condition + pushdown. */ + for (ulint i = 0; i < n_fields; i++) { + const Field* field = table->field[i]; + const bool is_v = !field->stored_in_db(); + if (is_v && skip_virtual) { + num_v++; + continue; + } + + bool index_contains = index->contains_col_or_prefix( + is_v ? num_v : i - num_v, is_v); + + if (!build_template_needs_field_in_icp( + index, m_prebuilt, index_contains, + is_v ? num_v : i - num_v, is_v)) { + /* Not needed in ICP */ + if (!whole_row) { + field = build_template_needs_field( + index_contains, + m_prebuilt->read_just_key, + fetch_all_in_key, + fetch_primary_key_cols, + index, table, i, num_v); + if (!field) { + if (is_v) { + num_v++; + } + continue; + } + } + + ut_d(mysql_row_templ_t* templ =) + build_template_field( + m_prebuilt, clust_index, index, + table, field, i - num_v, num_v); + ut_ad(templ->is_virtual == (ulint)is_v); + + if (is_v) { + num_v++; + } + } + } + } else { +no_icp: + /* No index condition pushdown */ + m_prebuilt->idx_cond = NULL; + ut_ad(num_v == 0); + + for (ulint i = 0; i < n_fields; i++) { + const Field* field = table->field[i]; + const bool is_v = !field->stored_in_db(); + + if (whole_row) { + if (is_v && skip_virtual) { + num_v++; + continue; + } + /* Even this is whole_row, if the seach is + on a virtual column, and read_just_key is + set, and field is not in this index, we + will not try to fill the value since they + are not stored in such index nor in the + cluster index. */ + if (is_v + && m_prebuilt->read_just_key + && !m_prebuilt->index->contains_col_or_prefix( + num_v, true)) + { + /* Turn off ROW_MYSQL_WHOLE_ROW */ + m_prebuilt->template_type = + ROW_MYSQL_REC_FIELDS; + num_v++; + continue; + } + } else { + if (is_v + && (skip_virtual || index->is_primary())) { + num_v++; + continue; + } + + bool contain = index->contains_col_or_prefix( + is_v ? num_v: i - num_v, is_v); + + field = build_template_needs_field( + contain, + m_prebuilt->read_just_key, + fetch_all_in_key, + fetch_primary_key_cols, + index, table, i, num_v); + if (!field) { + if (is_v) { + num_v++; + } + continue; + } + } + + ut_d(mysql_row_templ_t* templ =) + build_template_field( + m_prebuilt, clust_index, index, + table, field, i - num_v, num_v); + ut_ad(templ->is_virtual == (ulint)is_v); + if (is_v) { + num_v++; + } + } + } + + if (index != clust_index && m_prebuilt->need_to_access_clustered) { + /* Change rec_field_no's to correspond to the clustered index + record */ + for (ulint i = 0; i < m_prebuilt->n_template; i++) { + mysql_row_templ_t* templ + = &m_prebuilt->mysql_template[i]; + + templ->rec_field_no = templ->clust_rec_field_no; + } + } +} + +/********************************************************************//** +This special handling is really to overcome the limitations of MySQL's +binlogging. We need to eliminate the non-determinism that will arise in +INSERT ... SELECT type of statements, since MySQL binlog only stores the +min value of the autoinc interval. Once that is fixed we can get rid of +the special lock handling. +@return DB_SUCCESS if all OK else error code */ + +dberr_t +ha_innobase::innobase_lock_autoinc(void) +/*====================================*/ +{ + DBUG_ENTER("ha_innobase::innobase_lock_autoinc"); + dberr_t error = DB_SUCCESS; + + ut_ad(!srv_read_only_mode); + + switch (innobase_autoinc_lock_mode) { + case AUTOINC_NO_LOCKING: + /* Acquire only the AUTOINC mutex. */ + m_prebuilt->table->autoinc_mutex.wr_lock(); + break; + + case AUTOINC_NEW_STYLE_LOCKING: + /* For simple (single/multi) row INSERTs/REPLACEs and RBR + events, we fallback to the old style only if another + transaction has already acquired the AUTOINC lock on + behalf of a LOAD FILE or INSERT ... SELECT etc. type of + statement. */ + switch (thd_sql_command(m_user_thd)) { + case SQLCOM_INSERT: + case SQLCOM_REPLACE: + case SQLCOM_END: // RBR event + /* Acquire the AUTOINC mutex. */ + m_prebuilt->table->autoinc_mutex.wr_lock(); + /* We need to check that another transaction isn't + already holding the AUTOINC lock on the table. */ + if (!m_prebuilt->table->n_waiting_or_granted_auto_inc_locks) { + /* Do not fall back to old style locking. */ + DBUG_RETURN(error); + } + m_prebuilt->table->autoinc_mutex.wr_unlock(); + } + /* Use old style locking. */ + /* fall through */ + case AUTOINC_OLD_STYLE_LOCKING: + DBUG_EXECUTE_IF("die_if_autoinc_old_lock_style_used", + ut_ad(0);); + error = row_lock_table_autoinc_for_mysql(m_prebuilt); + + if (error == DB_SUCCESS) { + + /* Acquire the AUTOINC mutex. */ + m_prebuilt->table->autoinc_mutex.wr_lock(); + } + break; + + default: + ut_error; + } + + DBUG_RETURN(error); +} + +/********************************************************************//** +Store the autoinc value in the table. The autoinc value is only set if +it's greater than the existing autoinc value in the table. +@return DB_SUCCESS if all went well else error code */ + +dberr_t +ha_innobase::innobase_set_max_autoinc( +/*==================================*/ + ulonglong auto_inc) /*!< in: value to store */ +{ + dberr_t error; + + error = innobase_lock_autoinc(); + + if (error == DB_SUCCESS) { + + dict_table_autoinc_update_if_greater(m_prebuilt->table, auto_inc); + m_prebuilt->table->autoinc_mutex.wr_unlock(); + } + + return(error); +} + +/** @return whether the table is read-only */ +bool ha_innobase::is_read_only(bool altering_to_supported) const +{ + ut_ad(m_prebuilt->trx == thd_to_trx(m_user_thd)); + + if (high_level_read_only) + { + ib_senderrf(m_user_thd, IB_LOG_LEVEL_WARN, ER_READ_ONLY_MODE); + return true; + } + + if (altering_to_supported) + return false; + + if (!DICT_TF_GET_ZIP_SSIZE(m_prebuilt->table->flags) || + !innodb_read_only_compressed) + return false; + + ib_senderrf(m_user_thd, IB_LOG_LEVEL_WARN, ER_UNSUPPORTED_COMPRESSED_TABLE); + return true; +} + +/********************************************************************//** +Stores a row in an InnoDB database, to the table specified in this +handle. +@return error code */ + +int +ha_innobase::write_row( +/*===================*/ + const uchar* record) /*!< in: a row in MySQL format */ +{ + dberr_t error; +#ifdef WITH_WSREP + bool wsrep_auto_inc_inserted= false; +#endif + int error_result = 0; + bool auto_inc_used = false; + mariadb_set_stats set_stats_temporary(handler_stats); + + DBUG_ENTER("ha_innobase::write_row"); + + trx_t* trx = thd_to_trx(m_user_thd); + + /* Validation checks before we commence write_row operation. */ + if (is_read_only()) { + DBUG_RETURN(HA_ERR_TABLE_READONLY); + } + + if (!trx_is_started(trx)) { + trx->will_lock = true; + } + + ins_mode_t vers_set_fields; + /* Handling of Auto-Increment Columns. */ + if (table->next_number_field && record == table->record[0]) { + + /* Reset the error code before calling + innobase_get_auto_increment(). */ + m_prebuilt->autoinc_error = DB_SUCCESS; + +#ifdef WITH_WSREP + wsrep_auto_inc_inserted = trx->is_wsrep() + && wsrep_drupal_282555_workaround + && table->next_number_field->val_int() == 0; +#endif + + if ((error_result = update_auto_increment())) { + /* We don't want to mask autoinc overflow errors. */ + + /* Handle the case where the AUTOINC sub-system + failed during initialization. */ + if (m_prebuilt->autoinc_error == DB_UNSUPPORTED) { + error_result = ER_AUTOINC_READ_FAILED; + /* Set the error message to report too. */ + my_error(ER_AUTOINC_READ_FAILED, MYF(0)); + goto func_exit; + } else if (m_prebuilt->autoinc_error != DB_SUCCESS) { + error = m_prebuilt->autoinc_error; + goto report_error; + } + + /* MySQL errors are passed straight back. */ + goto func_exit; + } + + auto_inc_used = true; + } + + /* Prepare INSERT graph that will be executed for actual INSERT + (This is a one time operation) */ + if (m_prebuilt->mysql_template == NULL + || m_prebuilt->template_type != ROW_MYSQL_WHOLE_ROW) { + + /* Build the template used in converting quickly between + the two database formats */ + + build_template(true); + } + + vers_set_fields = table->versioned_write(VERS_TRX_ID) ? + ROW_INS_VERSIONED : ROW_INS_NORMAL; + + /* Execute insert graph that will result in actual insert. */ + error = row_insert_for_mysql((byte*) record, m_prebuilt, vers_set_fields); + + DEBUG_SYNC(m_user_thd, "ib_after_row_insert"); + + /* Handling of errors related to auto-increment. */ + if (auto_inc_used) { + ulonglong auto_inc; + + /* Note the number of rows processed for this statement, used + by get_auto_increment() to determine the number of AUTO-INC + values to reserve. This is only useful for a mult-value INSERT + and is a statement level counter. */ + if (trx->n_autoinc_rows > 0) { + --trx->n_autoinc_rows; + } + + /* Get the value that MySQL attempted to store in the table.*/ + auto_inc = table->next_number_field->val_uint(); + + switch (error) { + case DB_DUPLICATE_KEY: + + /* A REPLACE command and LOAD DATA INFILE REPLACE + handle a duplicate key error themselves, but we + must update the autoinc counter if we are performing + those statements. */ + + switch (thd_sql_command(m_user_thd)) { + case SQLCOM_LOAD: + if (!trx->duplicates) { + break; + } + + case SQLCOM_REPLACE: + case SQLCOM_INSERT_SELECT: + case SQLCOM_REPLACE_SELECT: + goto set_max_autoinc; + +#ifdef WITH_WSREP + /* workaround for LP bug #355000, retrying the insert */ + case SQLCOM_INSERT: + + WSREP_DEBUG("DUPKEY error for autoinc\n" + "THD %ld, value %llu, off %llu inc %llu", + thd_get_thread_id(m_user_thd), + auto_inc, + m_prebuilt->autoinc_offset, + m_prebuilt->autoinc_increment); + + if (wsrep_auto_inc_inserted && + wsrep_thd_retry_counter(m_user_thd) == 0 && + !thd_test_options(m_user_thd, + OPTION_NOT_AUTOCOMMIT | + OPTION_BEGIN)) { + WSREP_DEBUG( + "retrying insert: %s", + wsrep_thd_query(m_user_thd)); + error= DB_SUCCESS; + wsrep_thd_self_abort(m_user_thd); + /* jump straight to func exit over + * later wsrep hooks */ + goto func_exit; + } + break; +#endif /* WITH_WSREP */ + + default: + break; + } + + break; + + case DB_SUCCESS: + /* If the actual value inserted is greater than + the upper limit of the interval, then we try and + update the table upper limit. Note: last_value + will be 0 if get_auto_increment() was not called. */ + + if (auto_inc >= m_prebuilt->autoinc_last_value) { +set_max_autoinc: + /* We need the upper limit of the col type to check for + whether we update the table autoinc counter or not. */ + ulonglong col_max_value = + table->next_number_field->get_max_int_value(); + + /* This should filter out the negative + values set explicitly by the user. */ + if (auto_inc <= col_max_value) { + ut_ad(m_prebuilt->autoinc_increment > 0); + + ulonglong offset; + ulonglong increment; + dberr_t err; + + offset = m_prebuilt->autoinc_offset; + increment = m_prebuilt->autoinc_increment; + + auto_inc = innobase_next_autoinc( + auto_inc, 1, increment, offset, + col_max_value); + + err = innobase_set_max_autoinc( + auto_inc); + + if (err != DB_SUCCESS) { + error = err; + } + } + } + break; + default: + break; + } + } + +report_error: + /* Cleanup and exit. */ + if (error == DB_TABLESPACE_DELETED) { + ib_senderrf( + trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_DISCARDED, + table->s->table_name.str); + } + + error_result = convert_error_code_to_mysql( + error, m_prebuilt->table->flags, m_user_thd); + +#ifdef WITH_WSREP + if (!error_result && trx->is_wsrep() + && !trx->is_bulk_insert() + && wsrep_thd_is_local(m_user_thd) + && !wsrep_thd_ignore_table(m_user_thd) + && !wsrep_consistency_check(m_user_thd) + && (thd_sql_command(m_user_thd) != SQLCOM_CREATE_TABLE) + && (thd_sql_command(m_user_thd) != SQLCOM_LOAD || + thd_binlog_format(m_user_thd) == BINLOG_FORMAT_ROW)) { + if (wsrep_append_keys(m_user_thd, WSREP_SERVICE_KEY_EXCLUSIVE, + record, + NULL)) { + DBUG_PRINT("wsrep", ("row key failed")); + error_result = HA_ERR_INTERNAL_ERROR; + goto func_exit; + } + } +#endif /* WITH_WSREP */ + + if (error_result == HA_FTS_INVALID_DOCID) { + my_error(HA_FTS_INVALID_DOCID, MYF(0)); + } + +func_exit: + DBUG_RETURN(error_result); +} + +/** Fill the update vector's "old_vrow" field for those non-updated, +but indexed columns. Such columns could stil present in the virtual +index rec fields even if they are not updated (some other fields updated), +so needs to be logged. +@param[in] prebuilt InnoDB prebuilt struct +@param[in,out] vfield field to filled +@param[in] o_len actual column length +@param[in,out] col column to be filled +@param[in] old_mysql_row_col MySQL old field ptr +@param[in] col_pack_len MySQL field col length +@param[in,out] buf buffer for a converted integer value +@return used buffer ptr from row_mysql_store_col_in_innobase_format() */ +static +byte* +innodb_fill_old_vcol_val( + row_prebuilt_t* prebuilt, + dfield_t* vfield, + ulint o_len, + dict_col_t* col, + const byte* old_mysql_row_col, + ulint col_pack_len, + byte* buf) +{ + dict_col_copy_type( + col, dfield_get_type(vfield)); + if (o_len != UNIV_SQL_NULL) { + + buf = row_mysql_store_col_in_innobase_format( + vfield, + buf, + TRUE, + old_mysql_row_col, + col_pack_len, + dict_table_is_comp(prebuilt->table)); + } else { + dfield_set_null(vfield); + } + + return(buf); +} + +/** Calculate an update vector corresponding to the changes +between old_row and new_row. +@param[out] uvect update vector +@param[in] old_row current row in MySQL format +@param[in] new_row intended updated row in MySQL format +@param[in] table MySQL table handle +@param[in,out] upd_buff buffer to use for converted values +@param[in] buff_len length of upd_buff +@param[in,out] prebuilt InnoDB execution context +@param[out] auto_inc updated AUTO_INCREMENT value, or 0 if none +@return DB_SUCCESS or error code */ +static +dberr_t +calc_row_difference( + upd_t* uvect, + const uchar* old_row, + const uchar* new_row, + TABLE* table, + uchar* upd_buff, + ulint buff_len, + row_prebuilt_t* prebuilt, + ib_uint64_t& auto_inc) +{ + uchar* original_upd_buff = upd_buff; + Field* field; + enum_field_types field_mysql_type; + ulint o_len; + ulint n_len; + ulint col_pack_len; + const byte* new_mysql_row_col; + const byte* old_mysql_row_col; + const byte* o_ptr; + const byte* n_ptr; + byte* buf; + upd_field_t* ufield; + ulint col_type; + ulint n_changed = 0; + dfield_t dfield; + dict_index_t* clust_index; + ibool changes_fts_column = FALSE; + ibool changes_fts_doc_col = FALSE; + trx_t* const trx = prebuilt->trx; + doc_id_t doc_id = FTS_NULL_DOC_ID; + uint16_t num_v = 0; +#ifndef DBUG_OFF + uint vers_fields = 0; +#endif + prebuilt->versioned_write = table->versioned_write(VERS_TRX_ID); + const bool skip_virtual = ha_innobase::omits_virtual_cols(*table->s); + + ut_ad(!srv_read_only_mode); + + clust_index = dict_table_get_first_index(prebuilt->table); + auto_inc = 0; + + /* We use upd_buff to convert changed fields */ + buf = (byte*) upd_buff; + + for (uint i = 0; i < table->s->fields; i++) { + field = table->field[i]; + +#ifndef DBUG_OFF + if (!field->vers_sys_field() + && !field->vers_update_unversioned()) { + ++vers_fields; + } +#endif + + const bool is_virtual = !field->stored_in_db(); + if (is_virtual && skip_virtual) { + num_v++; + continue; + } + dict_col_t* col = is_virtual + ? &prebuilt->table->v_cols[num_v].m_col + : &prebuilt->table->cols[i - num_v]; + + o_ptr = (const byte*) old_row + get_field_offset(table, field); + n_ptr = (const byte*) new_row + get_field_offset(table, field); + + /* Use new_mysql_row_col and col_pack_len save the values */ + + new_mysql_row_col = n_ptr; + old_mysql_row_col = o_ptr; + col_pack_len = field->pack_length(); + + o_len = col_pack_len; + n_len = col_pack_len; + + /* We use o_ptr and n_ptr to dig up the actual data for + comparison. */ + + field_mysql_type = field->type(); + + col_type = col->mtype; + + switch (col_type) { + + case DATA_BLOB: + case DATA_GEOMETRY: + o_ptr = row_mysql_read_blob_ref(&o_len, o_ptr, o_len); + n_ptr = row_mysql_read_blob_ref(&n_len, n_ptr, n_len); + + break; + + case DATA_VARCHAR: + case DATA_BINARY: + case DATA_VARMYSQL: + if (field_mysql_type == MYSQL_TYPE_VARCHAR) { + /* This is a >= 5.0.3 type true VARCHAR where + the real payload data length is stored in + 1 or 2 bytes */ + + o_ptr = row_mysql_read_true_varchar( + &o_len, o_ptr, + (ulint) + (((Field_varstring*) field)->length_bytes)); + + n_ptr = row_mysql_read_true_varchar( + &n_len, n_ptr, + (ulint) + (((Field_varstring*) field)->length_bytes)); + } + + break; + default: + ; + } + + if (field_mysql_type == MYSQL_TYPE_LONGLONG + && prebuilt->table->fts + && innobase_strcasecmp( + field->field_name.str, FTS_DOC_ID_COL_NAME) == 0) { + doc_id = mach_read_uint64_little_endian(n_ptr); + if (doc_id == 0) { + return(DB_FTS_INVALID_DOCID); + } + } + + if (field->real_maybe_null()) { + if (field->is_null_in_record(old_row)) { + o_len = UNIV_SQL_NULL; + } + + if (field->is_null_in_record(new_row)) { + n_len = UNIV_SQL_NULL; + } + } + + if (is_virtual) { + /* If the virtual column is not indexed, + we shall ignore it for update */ + if (!col->ord_part) { + next: + num_v++; + continue; + } + + if (!uvect->old_vrow) { + uvect->old_vrow = dtuple_create_with_vcol( + uvect->heap, 0, prebuilt->table->n_v_cols); + } + + ulint max_field_len = DICT_MAX_FIELD_LEN_BY_FORMAT( + prebuilt->table); + + /* for virtual columns, we only materialize + its index, and index field length would not + exceed max_field_len. So continue if the + first max_field_len bytes are matched up */ + if (o_len != UNIV_SQL_NULL + && n_len != UNIV_SQL_NULL + && o_len >= max_field_len + && n_len >= max_field_len + && memcmp(o_ptr, n_ptr, max_field_len) == 0) { + dfield_t* vfield = dtuple_get_nth_v_field( + uvect->old_vrow, num_v); + buf = innodb_fill_old_vcol_val( + prebuilt, vfield, o_len, + col, old_mysql_row_col, + col_pack_len, buf); + goto next; + } + } + + if (o_len != n_len || (o_len != 0 && o_len != UNIV_SQL_NULL + && 0 != memcmp(o_ptr, n_ptr, o_len))) { + /* The field has changed */ + + ufield = uvect->fields + n_changed; + MEM_UNDEFINED(ufield, sizeof *ufield); + + /* Let us use a dummy dfield to make the conversion + from the MySQL column format to the InnoDB format */ + + + /* If the length of new geometry object is 0, means + this object is invalid geometry object, we need + to block it. */ + if (DATA_GEOMETRY_MTYPE(col_type) + && o_len != 0 && n_len == 0) { + return(DB_CANT_CREATE_GEOMETRY_OBJECT); + } + + if (n_len != UNIV_SQL_NULL) { + dict_col_copy_type( + col, dfield_get_type(&dfield)); + + buf = row_mysql_store_col_in_innobase_format( + &dfield, + (byte*) buf, + TRUE, + new_mysql_row_col, + col_pack_len, + dict_table_is_comp(prebuilt->table)); + dfield_copy(&ufield->new_val, &dfield); + } else { + dict_col_copy_type( + col, dfield_get_type(&ufield->new_val)); + dfield_set_null(&ufield->new_val); + } + + ufield->exp = NULL; + ufield->orig_len = 0; + if (is_virtual) { + dfield_t* vfield = dtuple_get_nth_v_field( + uvect->old_vrow, num_v); + upd_fld_set_virtual_col(ufield); + ufield->field_no = num_v; + + ut_ad(col->ord_part); + ufield->old_v_val = static_cast<dfield_t*>( + mem_heap_alloc( + uvect->heap, + sizeof *ufield->old_v_val)); + + if (!field->is_null_in_record(old_row)) { + if (n_len == UNIV_SQL_NULL) { + dict_col_copy_type( + col, dfield_get_type( + &dfield)); + } + + buf = row_mysql_store_col_in_innobase_format( + &dfield, + (byte*) buf, + TRUE, + old_mysql_row_col, + col_pack_len, + dict_table_is_comp( + prebuilt->table)); + dfield_copy(ufield->old_v_val, + &dfield); + dfield_copy(vfield, &dfield); + } else { + dict_col_copy_type( + col, dfield_get_type( + ufield->old_v_val)); + dfield_set_null(ufield->old_v_val); + dfield_set_null(vfield); + } + num_v++; + ut_ad(field != table->found_next_number_field); + } else { + ufield->field_no = static_cast<uint16_t>( + dict_col_get_clust_pos( + &prebuilt->table->cols + [i - num_v], + clust_index)); + ufield->old_v_val = NULL; + if (field != table->found_next_number_field + || dfield_is_null(&ufield->new_val)) { + } else { + auto_inc = field->val_uint(); + } + } + n_changed++; + + /* If an FTS indexed column was changed by this + UPDATE then we need to inform the FTS sub-system. + + NOTE: Currently we re-index all FTS indexed columns + even if only a subset of the FTS indexed columns + have been updated. That is the reason we are + checking only once here. Later we will need to + note which columns have been updated and do + selective processing. */ + if (prebuilt->table->fts != NULL && !is_virtual) { + ulint offset; + dict_table_t* innodb_table; + + innodb_table = prebuilt->table; + + if (!changes_fts_column) { + offset = row_upd_changes_fts_column( + innodb_table, ufield); + + if (offset != ULINT_UNDEFINED) { + changes_fts_column = TRUE; + } + } + + if (!changes_fts_doc_col) { + changes_fts_doc_col = + row_upd_changes_doc_id( + innodb_table, ufield); + } + } + } else if (is_virtual) { + dfield_t* vfield = dtuple_get_nth_v_field( + uvect->old_vrow, num_v); + buf = innodb_fill_old_vcol_val( + prebuilt, vfield, o_len, + col, old_mysql_row_col, + col_pack_len, buf); + ut_ad(col->ord_part); + num_v++; + } + } + + /* If the update changes a column with an FTS index on it, we + then add an update column node with a new document id to the + other changes. We piggy back our changes on the normal UPDATE + to reduce processing and IO overhead. */ + if (!prebuilt->table->fts) { + trx->fts_next_doc_id = 0; + } else if (changes_fts_column || changes_fts_doc_col) { + dict_table_t* innodb_table = prebuilt->table; + + ufield = uvect->fields + n_changed; + + if (!DICT_TF2_FLAG_IS_SET( + innodb_table, DICT_TF2_FTS_HAS_DOC_ID)) { + + /* If Doc ID is managed by user, and if any + FTS indexed column has been updated, its corresponding + Doc ID must also be updated. Otherwise, return + error */ + if (changes_fts_column && !changes_fts_doc_col) { + ib::warn() << "A new Doc ID must be supplied" + " while updating FTS indexed columns."; + return(DB_FTS_INVALID_DOCID); + } + + /* Doc ID must monotonically increase */ + ut_ad(innodb_table->fts->cache); + if (doc_id < prebuilt->table->fts->cache->next_doc_id) { + + ib::warn() << "FTS Doc ID must be larger than " + << innodb_table->fts->cache->next_doc_id + - 1 << " for table " + << innodb_table->name; + + return(DB_FTS_INVALID_DOCID); + } + + + trx->fts_next_doc_id = doc_id; + } else { + /* If the Doc ID is a hidden column, it can't be + changed by user */ + ut_ad(!changes_fts_doc_col); + + /* Doc ID column is hidden, a new Doc ID will be + generated by following fts_update_doc_id() call */ + trx->fts_next_doc_id = 0; + } + + fts_update_doc_id( + innodb_table, ufield, &trx->fts_next_doc_id); + + ++n_changed; + } else { + /* We have a Doc ID column, but none of FTS indexed + columns are touched, nor the Doc ID column, so set + fts_next_doc_id to UINT64_UNDEFINED, which means do not + update the Doc ID column */ + trx->fts_next_doc_id = UINT64_UNDEFINED; + } + + uvect->n_fields = n_changed; + uvect->info_bits = 0; + + ut_a(buf <= (byte*) original_upd_buff + buff_len); + + const TABLE_LIST *tl= table->pos_in_table_list; + const uint8 op_map= tl->trg_event_map | tl->slave_fk_event_map; + /* Used to avoid reading history in FK check on DELETE (see MDEV-16210). */ + prebuilt->upd_node->is_delete = + (op_map & trg2bit(TRG_EVENT_DELETE) + && table->versioned(VERS_TIMESTAMP)) + ? VERSIONED_DELETE : NO_DELETE; + + if (prebuilt->versioned_write) { + /* Guaranteed by CREATE TABLE, but anyway we make sure we + generate history only when there are versioned fields. */ + DBUG_ASSERT(vers_fields); + prebuilt->upd_node->vers_make_update(trx); + } + + ut_ad(uvect->validate()); + return(DB_SUCCESS); +} + +#ifdef WITH_WSREP +static +int +wsrep_calc_row_hash( +/*================*/ + byte* digest, /*!< in/out: md5 sum */ + const uchar* row, /*!< in: row in MySQL format */ + TABLE* table, /*!< in: table in MySQL data + dictionary */ + row_prebuilt_t* prebuilt) /*!< in: InnoDB prebuilt struct */ +{ + void *ctx = alloca(my_md5_context_size()); + my_md5_init(ctx); + + for (uint i = 0; i < table->s->fields; i++) { + byte null_byte=0; + byte true_byte=1; + unsigned is_unsigned; + + const Field* field = table->field[i]; + if (!field->stored_in_db()) { + continue; + } + + auto ptr = row + get_field_offset(table, field); + ulint len = field->pack_length(); + + switch (get_innobase_type_from_mysql_type(&is_unsigned, + field)) { + case DATA_BLOB: + ptr = row_mysql_read_blob_ref(&len, ptr, len); + + break; + + case DATA_VARCHAR: + case DATA_BINARY: + case DATA_VARMYSQL: + if (field->type() == MYSQL_TYPE_VARCHAR) { + /* This is a >= 5.0.3 type true VARCHAR where + the real payload data length is stored in + 1 or 2 bytes */ + + ptr = row_mysql_read_true_varchar( + &len, ptr, + (ulint) + (((Field_varstring*)field)->length_bytes)); + + } + + break; + default: + ; + } + /* + if (field->null_ptr && + field_in_record_is_null(table, field, (char*) row)) { + */ + + if (field->is_null_in_record(row)) { + my_md5_input(ctx, &null_byte, 1); + } else { + my_md5_input(ctx, &true_byte, 1); + my_md5_input(ctx, ptr, len); + } + } + + my_md5_result(ctx, digest); + + return(0); +} + +/** Append table-level exclusive key. +@param thd MySQL thread handle +@param table table +@retval false on success +@retval true on failure */ +ATTRIBUTE_COLD bool wsrep_append_table_key(MYSQL_THD thd, const dict_table_t &table) +{ + char db_buf[NAME_LEN + 1]; + char tbl_buf[NAME_LEN + 1]; + ulint db_buf_len, tbl_buf_len; + + if (!table.parse_name(db_buf, tbl_buf, &db_buf_len, &tbl_buf_len)) + { + WSREP_ERROR("Parse_name for table key append failed: %s", + wsrep_thd_query(thd)); + return true; + } + + /* Append table-level exclusive key */ + const int rcode = wsrep_thd_append_table_key(thd, db_buf, + tbl_buf, WSREP_SERVICE_KEY_EXCLUSIVE); + if (rcode) + { + WSREP_ERROR("Appending table key failed: %s, %d", + wsrep_thd_query(thd), rcode); + return true; + } + + return false; +} +#endif /* WITH_WSREP */ + +/** +Updates a row given as a parameter to a new value. Note that we are given +whole rows, not just the fields which are updated: this incurs some +overhead for CPU when we check which fields are actually updated. +TODO: currently InnoDB does not prevent the 'Halloween problem': +in a searched update a single row can get updated several times +if its index columns are updated! +@param[in] old_row Old row contents in MySQL format +@param[out] new_row Updated row contents in MySQL format +@return error number or 0 */ + +int +ha_innobase::update_row( + const uchar* old_row, + const uchar* new_row) +{ + int err; + + dberr_t error; + trx_t* trx = thd_to_trx(m_user_thd); + mariadb_set_stats set_stats_temporary(handler_stats); + + DBUG_ENTER("ha_innobase::update_row"); + + if (is_read_only()) { + DBUG_RETURN(HA_ERR_TABLE_READONLY); + } else if (!trx_is_started(trx)) { + trx->will_lock = true; + } + + if (m_upd_buf == NULL) { + ut_ad(m_upd_buf_size == 0); + + /* Create a buffer for packing the fields of a record. Why + table->reclength did not work here? Obviously, because char + fields when packed actually became 1 byte longer, when we also + stored the string length as the first byte. */ + + m_upd_buf_size = table->s->reclength + table->s->max_key_length + + MAX_REF_PARTS * 3; + + m_upd_buf = reinterpret_cast<uchar*>( + my_malloc(PSI_INSTRUMENT_ME, + m_upd_buf_size, + MYF(MY_WME))); + + if (m_upd_buf == NULL) { + m_upd_buf_size = 0; + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + } + } + + upd_t* uvect = row_get_prebuilt_update_vector(m_prebuilt); + ib_uint64_t autoinc; + + /* Build an update vector from the modified fields in the rows + (uses m_upd_buf of the handle) */ + + error = calc_row_difference( + uvect, old_row, new_row, table, m_upd_buf, m_upd_buf_size, + m_prebuilt, autoinc); + + if (error != DB_SUCCESS) { + goto func_exit; + } + + if (!uvect->n_fields) { + /* This is the same as success, but instructs + MySQL that the row is not really updated and it + should not increase the count of updated rows. + This is fix for http://bugs.mysql.com/29157 */ + DBUG_RETURN(HA_ERR_RECORD_IS_THE_SAME); + } else { + if (m_prebuilt->upd_node->is_delete) { + trx->fts_next_doc_id = 0; + } + + /* row_start was updated by vers_make_update() + in calc_row_difference() */ + error = row_update_for_mysql(m_prebuilt); + + if (error == DB_SUCCESS && m_prebuilt->versioned_write + /* Multiple UPDATE of same rows in single transaction create + historical rows only once. */ + && trx->id != table->vers_start_id()) { + /* UPDATE is not used by ALTER TABLE. Just precaution + as we don't need history generation for ALTER TABLE. */ + ut_ad(thd_sql_command(m_user_thd) != SQLCOM_ALTER_TABLE); + error = row_insert_for_mysql((byte*) old_row, + m_prebuilt, + ROW_INS_HISTORICAL); + } + } + + if (error == DB_SUCCESS && autoinc) { + /* A value for an AUTO_INCREMENT column + was specified in the UPDATE statement. */ + + /* We need the upper limit of the col type to check for + whether we update the table autoinc counter or not. */ + ulonglong col_max_value = + table->found_next_number_field->get_max_int_value(); + + /* This should filter out the negative + values set explicitly by the user. */ + if (autoinc <= col_max_value) { + ulonglong offset; + ulonglong increment; + + offset = m_prebuilt->autoinc_offset; + increment = m_prebuilt->autoinc_increment; + + autoinc = innobase_next_autoinc( + autoinc, 1, increment, offset, + col_max_value); + + error = innobase_set_max_autoinc(autoinc); + + if (m_prebuilt->table->persistent_autoinc) { + /* Update the PAGE_ROOT_AUTO_INC. Yes, we do + this even if dict_table_t::autoinc already was + greater than autoinc, because we cannot know + if any INSERT actually used (and wrote to + PAGE_ROOT_AUTO_INC) a value bigger than our + autoinc. */ + btr_write_autoinc(dict_table_get_first_index( + m_prebuilt->table), + autoinc); + } + } + } + +func_exit: + if (error == DB_FTS_INVALID_DOCID) { + err = HA_FTS_INVALID_DOCID; + my_error(HA_FTS_INVALID_DOCID, MYF(0)); + } else { + err = convert_error_code_to_mysql( + error, m_prebuilt->table->flags, m_user_thd); + } + +#ifdef WITH_WSREP + if (error == DB_SUCCESS && trx->is_wsrep() + && wsrep_thd_is_local(m_user_thd) + && !wsrep_thd_ignore_table(m_user_thd)) { + DBUG_PRINT("wsrep", ("update row key")); + + /* We use table-level exclusive key for SEQUENCES + and normal key append for others. */ + if (table->s->table_type == TABLE_TYPE_SEQUENCE) { + if (wsrep_append_table_key(m_user_thd, *m_prebuilt->table)) + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } else if (wsrep_append_keys(m_user_thd, + wsrep_protocol_version >= 4 + ? WSREP_SERVICE_KEY_UPDATE + : WSREP_SERVICE_KEY_EXCLUSIVE, + old_row, new_row)) { + WSREP_DEBUG("WSREP: UPDATE_ROW_KEY FAILED"); + DBUG_PRINT("wsrep", ("row key failed")); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } + } +#endif /* WITH_WSREP */ + + DBUG_RETURN(err); +} + +/**********************************************************************//** +Deletes a row given as the parameter. +@return error number or 0 */ + +int +ha_innobase::delete_row( +/*====================*/ + const uchar* record) /*!< in: a row in MySQL format */ +{ + dberr_t error; + trx_t* trx = thd_to_trx(m_user_thd); + mariadb_set_stats set_stats_temporary(handler_stats); + + DBUG_ENTER("ha_innobase::delete_row"); + + if (is_read_only()) { + DBUG_RETURN(HA_ERR_TABLE_READONLY); + } else if (!trx_is_started(trx)) { + trx->will_lock = true; + } + + if (!m_prebuilt->upd_node) { + row_get_prebuilt_update_vector(m_prebuilt); + } + + /* This is a delete */ + m_prebuilt->upd_node->is_delete = table->versioned_write(VERS_TRX_ID) + && table->vers_end_field()->is_max() + && trx->id != table->vers_start_id() + ? VERSIONED_DELETE + : PLAIN_DELETE; + trx->fts_next_doc_id = 0; + + error = row_update_for_mysql(m_prebuilt); + +#ifdef WITH_WSREP + if (error == DB_SUCCESS && trx->is_wsrep() + && wsrep_thd_is_local(m_user_thd) + && !wsrep_thd_ignore_table(m_user_thd)) { + if (wsrep_append_keys(m_user_thd, WSREP_SERVICE_KEY_EXCLUSIVE, + record, + NULL)) { + DBUG_PRINT("wsrep", ("delete fail")); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } + } +#endif /* WITH_WSREP */ + DBUG_RETURN(convert_error_code_to_mysql( + error, m_prebuilt->table->flags, m_user_thd)); +} + +/**********************************************************************//** +Removes a new lock set on a row, if it was not read optimistically. This can +be called after a row has been read in the processing of an UPDATE or a DELETE +query. */ + +void +ha_innobase::unlock_row(void) +/*=========================*/ +{ + DBUG_ENTER("ha_innobase::unlock_row"); + + if (m_prebuilt->select_lock_type == LOCK_NONE) { + DBUG_VOID_RETURN; + } + + ut_ad(trx_state_eq(m_prebuilt->trx, TRX_STATE_ACTIVE, true)); + + switch (m_prebuilt->row_read_type) { + case ROW_READ_WITH_LOCKS: + if (m_prebuilt->trx->isolation_level > TRX_ISO_READ_COMMITTED) + break; + /* fall through */ + case ROW_READ_TRY_SEMI_CONSISTENT: + row_unlock_for_mysql(m_prebuilt, FALSE); + break; + case ROW_READ_DID_SEMI_CONSISTENT: + m_prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; + break; + } + + DBUG_VOID_RETURN; +} + +/* See handler.h and row0mysql.h for docs on this function. */ + +bool +ha_innobase::was_semi_consistent_read(void) +/*=======================================*/ +{ + return(m_prebuilt->row_read_type == ROW_READ_DID_SEMI_CONSISTENT); +} + +/* See handler.h and row0mysql.h for docs on this function. */ +void ha_innobase::try_semi_consistent_read(bool yes) +{ + ut_ad(m_prebuilt->trx == thd_to_trx(ha_thd())); + /* Row read type is set to semi consistent read if this was + requested by the SQL layer and the transaction isolation level is + READ UNCOMMITTED or READ COMMITTED. */ + m_prebuilt->row_read_type = yes + && m_prebuilt->trx->isolation_level <= TRX_ISO_READ_COMMITTED + ? ROW_READ_TRY_SEMI_CONSISTENT + : ROW_READ_WITH_LOCKS; +} + +/******************************************************************//** +Initializes a handle to use an index. +@return 0 or error number */ + +int +ha_innobase::index_init( +/*====================*/ + uint keynr, /*!< in: key (index) number */ + bool) +{ + DBUG_ENTER("index_init"); + + DBUG_RETURN(change_active_index(keynr)); +} + +/******************************************************************//** +Currently does nothing. +@return 0 */ + +int +ha_innobase::index_end(void) +/*========================*/ +{ + DBUG_ENTER("index_end"); + + active_index = MAX_KEY; + + in_range_check_pushed_down = FALSE; + + m_ds_mrr.dsmrr_close(); + + DBUG_RETURN(0); +} + +/*********************************************************************//** +Converts a search mode flag understood by MySQL to a flag understood +by InnoDB. */ +page_cur_mode_t +convert_search_mode_to_innobase( +/*============================*/ + ha_rkey_function find_flag) +{ + switch (find_flag) { + case HA_READ_KEY_EXACT: + /* this does not require the index to be UNIQUE */ + case HA_READ_KEY_OR_NEXT: + return(PAGE_CUR_GE); + case HA_READ_AFTER_KEY: + return(PAGE_CUR_G); + case HA_READ_BEFORE_KEY: + return(PAGE_CUR_L); + case HA_READ_KEY_OR_PREV: + case HA_READ_PREFIX_LAST: + case HA_READ_PREFIX_LAST_OR_PREV: + return(PAGE_CUR_LE); + case HA_READ_MBR_CONTAIN: + return(PAGE_CUR_CONTAIN); + case HA_READ_MBR_INTERSECT: + return(PAGE_CUR_INTERSECT); + case HA_READ_MBR_WITHIN: + return(PAGE_CUR_WITHIN); + case HA_READ_MBR_DISJOINT: + return(PAGE_CUR_DISJOINT); + case HA_READ_MBR_EQUAL: + return(PAGE_CUR_MBR_EQUAL); + case HA_READ_PREFIX: + return(PAGE_CUR_UNSUPP); + /* do not use "default:" in order to produce a gcc warning: + enumeration value '...' not handled in switch + (if -Wswitch or -Wall is used) */ + } + + my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "this functionality"); + + return(PAGE_CUR_UNSUPP); +} + +/* + BACKGROUND INFO: HOW A SELECT SQL QUERY IS EXECUTED + --------------------------------------------------- +The following does not cover all the details, but explains how we determine +the start of a new SQL statement, and what is associated with it. + +For each table in the database the MySQL interpreter may have several +table handle instances in use, also in a single SQL query. For each table +handle instance there is an InnoDB 'm_prebuilt' struct which contains most +of the InnoDB data associated with this table handle instance. + + A) if the user has not explicitly set any MySQL table level locks: + + 1) MySQL calls ::external_lock to set an 'intention' table level lock on +the table of the handle instance. There we set +m_prebuilt->sql_stat_start = TRUE. The flag sql_stat_start should be set +true if we are taking this table handle instance to use in a new SQL +statement issued by the user. We also increment trx->n_mysql_tables_in_use. + + 2) If m_prebuilt->sql_stat_start == TRUE we 'pre-compile' the MySQL search +instructions to m_prebuilt->template of the table handle instance in +::index_read. The template is used to save CPU time in large joins. + + 3) In row_search_mvcc(), if m_prebuilt->sql_stat_start is true, we +allocate a new consistent read view for the trx if it does not yet have one, +or in the case of a locking read, set an InnoDB 'intention' table level +lock on the table. + + 4) We do the SELECT. MySQL may repeatedly call ::index_read for the +same table handle instance, if it is a join. + + 5) When the SELECT ends, MySQL removes its intention table level locks +in ::external_lock. When trx->n_mysql_tables_in_use drops to zero, + (a) we execute a COMMIT there if the autocommit is on, + (b) we also release possible 'SQL statement level resources' InnoDB may +have for this SQL statement. The MySQL interpreter does NOT execute +autocommit for pure read transactions, though it should. That is why the +table handler in that case has to execute the COMMIT in ::external_lock. + + B) If the user has explicitly set MySQL table level locks, then MySQL +does NOT call ::external_lock at the start of the statement. To determine +when we are at the start of a new SQL statement we at the start of +::index_read also compare the query id to the latest query id where the +table handle instance was used. If it has changed, we know we are at the +start of a new SQL statement. Since the query id can theoretically +overwrap, we use this test only as a secondary way of determining the +start of a new SQL statement. */ + + +/**********************************************************************//** +Positions an index cursor to the index specified in the handle. Fetches the +row if any. +@return 0, HA_ERR_KEY_NOT_FOUND, or error number */ + +int +ha_innobase::index_read( +/*====================*/ + uchar* buf, /*!< in/out: buffer for the returned + row */ + const uchar* key_ptr, /*!< in: key value; if this is NULL + we position the cursor at the + start or end of index; this can + also contain an InnoDB row id, in + which case key_len is the InnoDB + row id length; the key value can + also be a prefix of a full key value, + and the last column can be a prefix + of a full column */ + uint key_len,/*!< in: key value length */ + enum ha_rkey_function find_flag)/*!< in: search flags from my_base.h */ +{ + DBUG_ENTER("index_read"); + mariadb_set_stats set_stats_temporary(handler_stats); + DEBUG_SYNC_C("ha_innobase_index_read_begin"); + + ut_a(m_prebuilt->trx == thd_to_trx(m_user_thd)); + ut_ad(key_len != 0 || find_flag != HA_READ_KEY_EXACT); + + dict_index_t* index = m_prebuilt->index; + + if (index == NULL || index->is_corrupted()) { + m_prebuilt->index_usable = FALSE; + DBUG_RETURN(HA_ERR_CRASHED); + } + + if (!m_prebuilt->index_usable) { + DBUG_RETURN(index->is_corrupted() + ? HA_ERR_INDEX_CORRUPT + : HA_ERR_TABLE_DEF_CHANGED); + } + + if (index->type & DICT_FTS) { + DBUG_RETURN(HA_ERR_KEY_NOT_FOUND); + } + + /* For R-Tree index, we will always place the page lock to + pages being searched */ + if (index->is_spatial() && !m_prebuilt->trx->will_lock) { + if (trx_is_started(m_prebuilt->trx)) { + DBUG_RETURN(HA_ERR_READ_ONLY_TRANSACTION); + } else { + m_prebuilt->trx->will_lock = true; + } + } + + /* Note that if the index for which the search template is built is not + necessarily m_prebuilt->index, but can also be the clustered index */ + + if (m_prebuilt->sql_stat_start) { + build_template(false); + } + + if (key_ptr != NULL) { + /* Convert the search key value to InnoDB format into + m_prebuilt->search_tuple */ + + row_sel_convert_mysql_key_to_innobase( + m_prebuilt->search_tuple, + m_prebuilt->srch_key_val1, + m_prebuilt->srch_key_val_len, + index, + (byte*) key_ptr, + (ulint) key_len); + + DBUG_ASSERT(m_prebuilt->search_tuple->n_fields > 0); + } else { + /* We position the cursor to the last or the first entry + in the index */ + + dtuple_set_n_fields(m_prebuilt->search_tuple, 0); + } + + page_cur_mode_t mode = convert_search_mode_to_innobase(find_flag); + + ulint match_mode = 0; + + if (find_flag == HA_READ_KEY_EXACT) { + + match_mode = ROW_SEL_EXACT; + + } else if (find_flag == HA_READ_PREFIX_LAST) { + + match_mode = ROW_SEL_EXACT_PREFIX; + } + + m_last_match_mode = (uint) match_mode; + + dberr_t ret = mode == PAGE_CUR_UNSUPP ? DB_UNSUPPORTED + : row_search_mvcc(buf, mode, m_prebuilt, match_mode, 0); + + DBUG_EXECUTE_IF("ib_select_query_failure", ret = DB_ERROR;); + + int error; + + switch (ret) { + case DB_SUCCESS: + error = 0; + table->status = 0; + break; + + case DB_RECORD_NOT_FOUND: + error = HA_ERR_KEY_NOT_FOUND; + table->status = STATUS_NOT_FOUND; + break; + + case DB_END_OF_INDEX: + error = HA_ERR_KEY_NOT_FOUND; + table->status = STATUS_NOT_FOUND; + break; + + case DB_TABLESPACE_DELETED: + ib_senderrf( + m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_DISCARDED, + table->s->table_name.str); + + table->status = STATUS_NOT_FOUND; + error = HA_ERR_TABLESPACE_MISSING; + break; + + case DB_TABLESPACE_NOT_FOUND: + + ib_senderrf( + m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_MISSING, + table->s->table_name.str); + + table->status = STATUS_NOT_FOUND; + error = HA_ERR_TABLESPACE_MISSING; + break; + + default: + error = convert_error_code_to_mysql( + ret, m_prebuilt->table->flags, m_user_thd); + + table->status = STATUS_NOT_FOUND; + break; + } + + DBUG_RETURN(error); +} + +/*******************************************************************//** +The following functions works like index_read, but it find the last +row with the current key value or prefix. +@return 0, HA_ERR_KEY_NOT_FOUND, or an error code */ + +int +ha_innobase::index_read_last( +/*=========================*/ + uchar* buf, /*!< out: fetched row */ + const uchar* key_ptr,/*!< in: key value, or a prefix of a full + key value */ + uint key_len)/*!< in: length of the key val or prefix + in bytes */ +{ + return(index_read(buf, key_ptr, key_len, HA_READ_PREFIX_LAST)); +} + +/********************************************************************//** +Get the index for a handle. Does not change active index. +@return NULL or index instance. */ + +dict_index_t* +ha_innobase::innobase_get_index( +/*============================*/ + uint keynr) /*!< in: use this index; MAX_KEY means always + clustered index, even if it was internally + generated by InnoDB */ +{ + KEY* key = NULL; + dict_table_t* ib_table = m_prebuilt->table; + dict_index_t* index; + + DBUG_ENTER("innobase_get_index"); + + if (keynr != MAX_KEY && table->s->keys > 0) { + key = &table->key_info[keynr]; + index = dict_table_get_index_on_name(ib_table, key->name.str); + } else { + index = dict_table_get_first_index(ib_table); + } + + if (index == NULL) { + sql_print_error( + "InnoDB could not find key no %u with name %s" + " from dict cache for table %s", + keynr, key ? key->name.str : "NULL", + ib_table->name.m_name); + } + + DBUG_RETURN(index); +} + +/********************************************************************//** +Changes the active index of a handle. +@return 0 or error code */ + +int +ha_innobase::change_active_index( +/*=============================*/ + uint keynr) /*!< in: use this index; MAX_KEY means always clustered + index, even if it was internally generated by + InnoDB */ +{ + DBUG_ENTER("change_active_index"); + + ut_ad(m_user_thd == ha_thd()); + ut_a(m_prebuilt->trx == thd_to_trx(m_user_thd)); + + active_index = keynr; + + m_prebuilt->index = innobase_get_index(keynr); + + if (m_prebuilt->index == NULL) { + sql_print_warning("InnoDB: change_active_index(%u) failed", + keynr); + m_prebuilt->index_usable = FALSE; + DBUG_RETURN(1); + } + + m_prebuilt->index_usable = row_merge_is_index_usable( + m_prebuilt->trx, m_prebuilt->index); + + if (!m_prebuilt->index_usable) { + if (m_prebuilt->index->is_corrupted()) { + char table_name[MAX_FULL_NAME_LEN + 1]; + + innobase_format_name( + table_name, sizeof table_name, + m_prebuilt->index->table->name.m_name); + + if (m_prebuilt->index->is_primary()) { + ut_ad(m_prebuilt->index->table->corrupted); + push_warning_printf( + m_user_thd, Sql_condition::WARN_LEVEL_WARN, + ER_TABLE_CORRUPT, + "InnoDB: Table %s is corrupted.", + table_name); + DBUG_RETURN(ER_TABLE_CORRUPT); + } else { + push_warning_printf( + m_user_thd, Sql_condition::WARN_LEVEL_WARN, + HA_ERR_INDEX_CORRUPT, + "InnoDB: Index %s for table %s is" + " marked as corrupted", + m_prebuilt->index->name(), + table_name); + DBUG_RETURN(HA_ERR_INDEX_CORRUPT); + } + } else { + push_warning_printf( + m_user_thd, Sql_condition::WARN_LEVEL_WARN, + HA_ERR_TABLE_DEF_CHANGED, + "InnoDB: insufficient history for index %u", + keynr); + } + + /* The caller seems to ignore this. Thus, we must check + this again in row_search_mvcc(). */ + DBUG_RETURN(convert_error_code_to_mysql(DB_MISSING_HISTORY, + 0, NULL)); + } + + ut_a(m_prebuilt->search_tuple != 0); + + /* Initialization of search_tuple is not needed for FT index + since FT search returns rank only. In addition engine should + be able to retrieve FTS_DOC_ID column value if necessary. */ + if (m_prebuilt->index->type & DICT_FTS) { + for (uint i = 0; i < table->s->fields; i++) { + if (m_prebuilt->read_just_key + && bitmap_is_set(table->read_set, i) + && !strcmp(table->s->field[i]->field_name.str, + FTS_DOC_ID_COL_NAME)) { + m_prebuilt->fts_doc_id_in_read_set = true; + break; + } + } + } else { + ulint n_fields = dict_index_get_n_unique_in_tree( + m_prebuilt->index); + + dtuple_set_n_fields(m_prebuilt->search_tuple, n_fields); + + dict_index_copy_types( + m_prebuilt->search_tuple, m_prebuilt->index, + n_fields); + + /* If it's FTS query and FTS_DOC_ID exists FTS_DOC_ID field is + always added to read_set. */ + m_prebuilt->fts_doc_id_in_read_set = m_prebuilt->in_fts_query + && m_prebuilt->read_just_key + && m_prebuilt->index->contains_col_or_prefix( + m_prebuilt->table->fts->doc_col, false); + } + + /* MySQL changes the active index for a handle also during some + queries, for example SELECT MAX(a), SUM(a) first retrieves the MAX() + and then calculates the sum. Previously we played safe and used + the flag ROW_MYSQL_WHOLE_ROW below, but that caused unnecessary + copying. Starting from MySQL-4.1 we use a more efficient flag here. */ + + build_template(false); + + DBUG_RETURN(0); +} + +/* @return true if it's necessary to switch current statement log format from +STATEMENT to ROW if binary log format is MIXED and autoincrement values +are changed in the statement */ +bool ha_innobase::autoinc_lock_mode_stmt_unsafe() const +{ + return innobase_autoinc_lock_mode == AUTOINC_NO_LOCKING; +} + +/***********************************************************************//** +Reads the next or previous row from a cursor, which must have previously been +positioned using index_read. +@return 0, HA_ERR_END_OF_FILE, or error number */ + +int +ha_innobase::general_fetch( +/*=======================*/ + uchar* buf, /*!< in/out: buffer for next row in MySQL + format */ + uint direction, /*!< in: ROW_SEL_NEXT or ROW_SEL_PREV */ + uint match_mode) /*!< in: 0, ROW_SEL_EXACT, or + ROW_SEL_EXACT_PREFIX */ +{ + DBUG_ENTER("general_fetch"); + + mariadb_set_stats set_stats_temporary(handler_stats); + const trx_t* trx = m_prebuilt->trx; + + ut_ad(trx == thd_to_trx(m_user_thd)); + + if (m_prebuilt->table->is_readable()) { + } else if (m_prebuilt->table->corrupted) { + DBUG_RETURN(HA_ERR_CRASHED); + } else { + DBUG_RETURN(m_prebuilt->table->space + ? HA_ERR_DECRYPTION_FAILED + : HA_ERR_NO_SUCH_TABLE); + } + + int error; + + switch (dberr_t ret = row_search_mvcc(buf, PAGE_CUR_UNSUPP, m_prebuilt, + match_mode, direction)) { + case DB_SUCCESS: + error = 0; + table->status = 0; + break; + case DB_RECORD_NOT_FOUND: + error = HA_ERR_END_OF_FILE; + table->status = STATUS_NOT_FOUND; + break; + case DB_END_OF_INDEX: + error = HA_ERR_END_OF_FILE; + table->status = STATUS_NOT_FOUND; + break; + case DB_TABLESPACE_DELETED: + ib_senderrf( + trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_DISCARDED, + table->s->table_name.str); + + table->status = STATUS_NOT_FOUND; + error = HA_ERR_TABLESPACE_MISSING; + break; + case DB_TABLESPACE_NOT_FOUND: + + ib_senderrf( + trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_MISSING, + table->s->table_name.str); + + table->status = STATUS_NOT_FOUND; + error = HA_ERR_TABLESPACE_MISSING; + break; + default: + error = convert_error_code_to_mysql( + ret, m_prebuilt->table->flags, m_user_thd); + + table->status = STATUS_NOT_FOUND; + break; + } + + DBUG_RETURN(error); +} + +/***********************************************************************//** +Reads the next row from a cursor, which must have previously been +positioned using index_read. +@return 0, HA_ERR_END_OF_FILE, or error number */ + +int +ha_innobase::index_next( +/*====================*/ + uchar* buf) /*!< in/out: buffer for next row in MySQL + format */ +{ + return(general_fetch(buf, ROW_SEL_NEXT, 0)); +} + +/*******************************************************************//** +Reads the next row matching to the key value given as the parameter. +@return 0, HA_ERR_END_OF_FILE, or error number */ + +int +ha_innobase::index_next_same( +/*=========================*/ + uchar* buf, /*!< in/out: buffer for the row */ + const uchar*, uint) +{ + return(general_fetch(buf, ROW_SEL_NEXT, m_last_match_mode)); +} + +/***********************************************************************//** +Reads the previous row from a cursor, which must have previously been +positioned using index_read. +@return 0, HA_ERR_END_OF_FILE, or error number */ + +int +ha_innobase::index_prev( +/*====================*/ + uchar* buf) /*!< in/out: buffer for previous row in MySQL format */ +{ + return(general_fetch(buf, ROW_SEL_PREV, 0)); +} + +/********************************************************************//** +Positions a cursor on the first record in an index and reads the +corresponding row to buf. +@return 0, HA_ERR_END_OF_FILE, or error code */ + +int +ha_innobase::index_first( +/*=====================*/ + uchar* buf) /*!< in/out: buffer for the row */ +{ + DBUG_ENTER("index_first"); + + int error = index_read(buf, NULL, 0, HA_READ_AFTER_KEY); + + /* MySQL does not seem to allow this to return HA_ERR_KEY_NOT_FOUND */ + + if (error == HA_ERR_KEY_NOT_FOUND) { + error = HA_ERR_END_OF_FILE; + } + + DBUG_RETURN(error); +} + +/********************************************************************//** +Positions a cursor on the last record in an index and reads the +corresponding row to buf. +@return 0, HA_ERR_END_OF_FILE, or error code */ + +int +ha_innobase::index_last( +/*====================*/ + uchar* buf) /*!< in/out: buffer for the row */ +{ + DBUG_ENTER("index_last"); + + int error = index_read(buf, NULL, 0, HA_READ_BEFORE_KEY); + + /* MySQL does not seem to allow this to return HA_ERR_KEY_NOT_FOUND */ + + if (error == HA_ERR_KEY_NOT_FOUND) { + error = HA_ERR_END_OF_FILE; + } + + DBUG_RETURN(error); +} + +/****************************************************************//** +Initialize a table scan. +@return 0 or error number */ + +int +ha_innobase::rnd_init( +/*==================*/ + bool scan) /*!< in: true if table/index scan FALSE otherwise */ +{ + int err; + + /* Store the active index value so that we can restore the original + value after a scan */ + + if (m_prebuilt->clust_index_was_generated) { + err = change_active_index(MAX_KEY); + } else { + err = change_active_index(m_primary_key); + } + + /* Don't use semi-consistent read in random row reads (by position). + This means we must disable semi_consistent_read if scan is false */ + + if (!scan) { + try_semi_consistent_read(0); + } + + m_start_of_scan = true; + + return(err); +} + +/*****************************************************************//** +Ends a table scan. +@return 0 or error number */ + +int +ha_innobase::rnd_end(void) +/*======================*/ +{ + return(index_end()); +} + +/*****************************************************************//** +Reads the next row in a table scan (also used to read the FIRST row +in a table scan). +@return 0, HA_ERR_END_OF_FILE, or error number */ + +int +ha_innobase::rnd_next( +/*==================*/ + uchar* buf) /*!< in/out: returns the row in this buffer, + in MySQL format */ +{ + int error; + DBUG_ENTER("rnd_next"); + + if (m_start_of_scan) { + error = index_first(buf); + + if (error == HA_ERR_KEY_NOT_FOUND) { + error = HA_ERR_END_OF_FILE; + } + + m_start_of_scan = false; + } else { + error = general_fetch(buf, ROW_SEL_NEXT, 0); + } + + DBUG_RETURN(error); +} + +/**********************************************************************//** +Fetches a row from the table based on a row reference. +@return 0, HA_ERR_KEY_NOT_FOUND, or error code */ + +int +ha_innobase::rnd_pos( +/*=================*/ + uchar* buf, /*!< in/out: buffer for the row */ + uchar* pos) /*!< in: primary key value of the row in the + MySQL format, or the row id if the clustered + index was internally generated by InnoDB; the + length of data in pos has to be ref_length */ +{ + DBUG_ENTER("rnd_pos"); + DBUG_DUMP("key", pos, ref_length); + + ut_a(m_prebuilt->trx == thd_to_trx(ha_thd())); + + /* Note that we assume the length of the row reference is fixed + for the table, and it is == ref_length */ + + int error = index_read(buf, pos, (uint)ref_length, HA_READ_KEY_EXACT); + + if (error != 0) { + DBUG_PRINT("error", ("Got error: %d", error)); + } + + DBUG_RETURN(error); +} + +/**********************************************************************//** +Initialize FT index scan +@return 0 or error number */ + +int +ha_innobase::ft_init() +/*==================*/ +{ + DBUG_ENTER("ft_init"); + + trx_t* trx = check_trx_exists(ha_thd()); + + /* FTS queries are not treated as autocommit non-locking selects. + This is because the FTS implementation can acquire locks behind + the scenes. This has not been verified but it is safer to treat + them as regular read only transactions for now. */ + + if (!trx_is_started(trx)) { + trx->will_lock = true; + } + + DBUG_RETURN(rnd_init(false)); +} + +/**********************************************************************//** +Initialize FT index scan +@return FT_INFO structure if successful or NULL */ + +FT_INFO* +ha_innobase::ft_init_ext( +/*=====================*/ + uint flags, /* in: */ + uint keynr, /* in: */ + String* key) /* in: */ +{ + NEW_FT_INFO* fts_hdl = NULL; + dict_index_t* index; + fts_result_t* result; + char buf_tmp[8192]; + ulint buf_tmp_used; + uint num_errors; + ulint query_len = key->length(); + const CHARSET_INFO* char_set = key->charset(); + const char* query = key->ptr(); + + if (UNIV_UNLIKELY(fts_enable_diag_print)) { + { + ib::info out; + out << "keynr=" << keynr << ", '"; + out.write(key->ptr(), key->length()); + } + + if (flags & FT_BOOL) { + ib::info() << "BOOL search"; + } else { + ib::info() << "NL search"; + } + } + + /* Multi byte character sets like utf32 and utf16 are not + compatible with some string function used. So to convert them + to uft8 before we proceed. */ + if (char_set->mbminlen != 1) { + buf_tmp_used = innobase_convert_string( + buf_tmp, sizeof(buf_tmp) - 1, + &my_charset_utf8mb3_general_ci, + query, query_len, (CHARSET_INFO*) char_set, + &num_errors); + + buf_tmp[buf_tmp_used] = 0; + query = buf_tmp; + query_len = buf_tmp_used; + } + + trx_t* trx = m_prebuilt->trx; + + /* FTS queries are not treated as autocommit non-locking selects. + This is because the FTS implementation can acquire locks behind + the scenes. This has not been verified but it is safer to treat + them as regular read only transactions for now. */ + + if (!trx_is_started(trx)) { + trx->will_lock = true; + } + + dict_table_t* ft_table = m_prebuilt->table; + + /* Table does not have an FTS index */ + if (!ft_table->fts || ib_vector_is_empty(ft_table->fts->indexes)) { + my_error(ER_TABLE_HAS_NO_FT, MYF(0)); + return(NULL); + } + + /* If tablespace is discarded, we should return here */ + if (!ft_table->space) { + my_error(ER_TABLESPACE_MISSING, MYF(0), table->s->db.str, + table->s->table_name.str); + return(NULL); + } + + if (keynr == NO_SUCH_KEY) { + /* FIXME: Investigate the NO_SUCH_KEY usage */ + index = reinterpret_cast<dict_index_t*> + (ib_vector_getp(ft_table->fts->indexes, 0)); + } else { + index = innobase_get_index(keynr); + } + + if (index == NULL || index->type != DICT_FTS) { + my_error(ER_TABLE_HAS_NO_FT, MYF(0)); + return(NULL); + } + + if (!(ft_table->fts->added_synced)) { + fts_init_index(ft_table, FALSE); + + ft_table->fts->added_synced = true; + } + + const byte* q = reinterpret_cast<const byte*>( + const_cast<char*>(query)); + + // FIXME: support ft_init_ext_with_hints(), pass LIMIT + dberr_t error = fts_query(trx, index, flags, q, query_len, &result); + + if (error != DB_SUCCESS) { + my_error(convert_error_code_to_mysql(error, 0, NULL), MYF(0)); + return(NULL); + } + + /* Allocate FTS handler, and instantiate it before return */ + fts_hdl = reinterpret_cast<NEW_FT_INFO*>( + my_malloc(PSI_INSTRUMENT_ME, sizeof(NEW_FT_INFO), MYF(0))); + + fts_hdl->please = const_cast<_ft_vft*>(&ft_vft_result); + fts_hdl->could_you = const_cast<_ft_vft_ext*>(&ft_vft_ext_result); + fts_hdl->ft_prebuilt = m_prebuilt; + fts_hdl->ft_result = result; + + /* FIXME: Re-evaluate the condition when Bug 14469540 is resolved */ + m_prebuilt->in_fts_query = true; + + return(reinterpret_cast<FT_INFO*>(fts_hdl)); +} + +/*****************************************************************//** +Set up search tuple for a query through FTS_DOC_ID_INDEX on +supplied Doc ID. This is used by MySQL to retrieve the documents +once the search result (Doc IDs) is available + +@return DB_SUCCESS or DB_INDEX_CORRUPT +*/ +static +dberr_t +innobase_fts_create_doc_id_key( +/*===========================*/ + dtuple_t* tuple, /* in/out: m_prebuilt->search_tuple */ + const dict_index_t* + index, /* in: index (FTS_DOC_ID_INDEX) */ + doc_id_t* doc_id) /* in/out: doc id to search, value + could be changed to storage format + used for search. */ +{ + doc_id_t temp_doc_id; + dfield_t* dfield = dtuple_get_nth_field(tuple, 0); + const ulint n_uniq = index->table->fts_n_uniq(); + + if (dict_index_get_n_unique(index) != n_uniq) + return DB_INDEX_CORRUPT; + + dtuple_set_n_fields(tuple, index->n_fields); + dict_index_copy_types(tuple, index, index->n_fields); + +#ifdef UNIV_DEBUG + /* The unique Doc ID field should be an eight-bytes integer */ + dict_field_t* field = dict_index_get_nth_field(index, 0); + ut_a(field->col->mtype == DATA_INT); + ut_ad(sizeof(*doc_id) == field->fixed_len); + ut_ad(!strcmp(index->name, FTS_DOC_ID_INDEX_NAME)); +#endif /* UNIV_DEBUG */ + + /* Convert to storage byte order */ + mach_write_to_8(reinterpret_cast<byte*>(&temp_doc_id), *doc_id); + *doc_id = temp_doc_id; + dfield_set_data(dfield, doc_id, sizeof(*doc_id)); + + if (n_uniq == 2) { + ut_ad(index->table->versioned()); + dfield = dtuple_get_nth_field(tuple, 1); + if (index->table->versioned_by_id()) { + dfield_set_data(dfield, trx_id_max_bytes, + sizeof(trx_id_max_bytes)); + } else { + dfield_set_data(dfield, timestamp_max_bytes, + sizeof(timestamp_max_bytes)); + } + } + + dtuple_set_n_fields_cmp(tuple, n_uniq); + + for (ulint i = n_uniq; i < index->n_fields; i++) { + dfield = dtuple_get_nth_field(tuple, i); + dfield_set_null(dfield); + } + return DB_SUCCESS; +} + +/**********************************************************************//** +Fetch next result from the FT result set +@return error code */ + +int +ha_innobase::ft_read( +/*=================*/ + uchar* buf) /*!< in/out: buf contain result row */ +{ + row_prebuilt_t* ft_prebuilt; + mariadb_set_stats set_stats_temporary(handler_stats); + + ft_prebuilt = reinterpret_cast<NEW_FT_INFO*>(ft_handler)->ft_prebuilt; + + ut_a(ft_prebuilt == m_prebuilt); + + fts_result_t* result; + + result = reinterpret_cast<NEW_FT_INFO*>(ft_handler)->ft_result; + + if (result->current == NULL) { + /* This is the case where the FTS query did not + contain and matching documents. */ + if (result->rankings_by_id != NULL) { + /* Now that we have the complete result, we + need to sort the document ids on their rank + calculation. */ + + fts_query_sort_result_on_rank(result); + + result->current = const_cast<ib_rbt_node_t*>( + rbt_first(result->rankings_by_rank)); + } else { + ut_a(result->current == NULL); + } + } else { + result->current = const_cast<ib_rbt_node_t*>( + rbt_next(result->rankings_by_rank, result->current)); + } + +next_record: + + if (result->current != NULL) { + doc_id_t search_doc_id; + dtuple_t* tuple = m_prebuilt->search_tuple; + + /* If we only need information from result we can return + without fetching the table row */ + if (ft_prebuilt->read_just_key) { +#ifdef MYSQL_STORE_FTS_DOC_ID + if (m_prebuilt->fts_doc_id_in_read_set) { + fts_ranking_t* ranking; + ranking = rbt_value(fts_ranking_t, + result->current); + innobase_fts_store_docid( + table, ranking->doc_id); + } +#endif + table->status= 0; + return(0); + } + + dict_index_t* index; + + index = m_prebuilt->table->fts_doc_id_index; + + /* Must find the index */ + ut_a(index != NULL); + + /* Switch to the FTS doc id index */ + m_prebuilt->index = index; + + fts_ranking_t* ranking = rbt_value( + fts_ranking_t, result->current); + + search_doc_id = ranking->doc_id; + + /* We pass a pointer of search_doc_id because it will be + converted to storage byte order used in the search + tuple. */ + dberr_t ret = innobase_fts_create_doc_id_key( + tuple, index, &search_doc_id); + + if (ret == DB_SUCCESS) { + ret = row_search_mvcc( + buf, PAGE_CUR_GE, m_prebuilt, + ROW_SEL_EXACT, 0); + } + + int error; + + switch (ret) { + case DB_SUCCESS: + error = 0; + table->status = 0; + break; + case DB_RECORD_NOT_FOUND: + result->current = const_cast<ib_rbt_node_t*>( + rbt_next(result->rankings_by_rank, + result->current)); + + if (!result->current) { + /* exhaust the result set, should return + HA_ERR_END_OF_FILE just like + ha_innobase::general_fetch() and/or + ha_innobase::index_first() etc. */ + error = HA_ERR_END_OF_FILE; + table->status = STATUS_NOT_FOUND; + } else { + goto next_record; + } + break; + case DB_END_OF_INDEX: + error = HA_ERR_END_OF_FILE; + table->status = STATUS_NOT_FOUND; + break; + case DB_TABLESPACE_DELETED: + + ib_senderrf( + m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_DISCARDED, + table->s->table_name.str); + + table->status = STATUS_NOT_FOUND; + error = HA_ERR_TABLESPACE_MISSING; + break; + case DB_TABLESPACE_NOT_FOUND: + + ib_senderrf( + m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_MISSING, + table->s->table_name.str); + + table->status = STATUS_NOT_FOUND; + error = HA_ERR_TABLESPACE_MISSING; + break; + default: + error = convert_error_code_to_mysql( + ret, 0, m_user_thd); + + table->status = STATUS_NOT_FOUND; + break; + } + + return(error); + } + + return(HA_ERR_END_OF_FILE); +} + +#ifdef WITH_WSREP +inline +const char* +wsrep_key_type_to_str(Wsrep_service_key_type type) +{ + switch (type) { + case WSREP_SERVICE_KEY_SHARED: + return "shared"; + case WSREP_SERVICE_KEY_REFERENCE: + return "reference"; + case WSREP_SERVICE_KEY_UPDATE: + return "update"; + case WSREP_SERVICE_KEY_EXCLUSIVE: + return "exclusive"; + }; + return "unknown"; +} + +extern dberr_t +wsrep_append_foreign_key( +/*===========================*/ + trx_t* trx, /*!< in: trx */ + dict_foreign_t* foreign, /*!< in: foreign key constraint */ + const rec_t* rec, /*!<in: clustered index record */ + dict_index_t* index, /*!<in: clustered index */ + bool referenced, /*!<in: is check for + referenced table */ + upd_node_t* upd_node, /*<!in: update node */ + bool pa_disable, /*<!in: disable parallel apply ?*/ + Wsrep_service_key_type key_type) /*!< in: access type of this key + (shared, exclusive, reference...) */ +{ + ut_ad(trx->is_wsrep()); + + if (!wsrep_thd_is_local(trx->mysql_thd)) + return DB_SUCCESS; + + if (upd_node && wsrep_protocol_version < 4) { + key_type = WSREP_SERVICE_KEY_SHARED; + } + + THD* thd = trx->mysql_thd; + + if (!foreign || + (!foreign->referenced_table && !foreign->foreign_table)) { + WSREP_INFO("FK: %s missing in: %s", + (!foreign ? "constraint" : + (!foreign->referenced_table ? + "referenced table" : "foreign table")), + wsrep_thd_query(thd)); + return DB_ERROR; + } + + ulint rcode = DB_SUCCESS; + char cache_key[513] = {'\0'}; + size_t cache_key_len = 0; + + if ( !((referenced) ? + foreign->referenced_table : foreign->foreign_table)) { + WSREP_DEBUG("pulling %s table into cache", + (referenced) ? "referenced" : "foreign"); + dict_sys.lock(SRW_LOCK_CALL); + + if (referenced) { + foreign->referenced_table = + dict_sys.load_table( + {foreign->referenced_table_name_lookup, + strlen(foreign-> + referenced_table_name_lookup) + }); + if (foreign->referenced_table) { + foreign->referenced_index = + dict_foreign_find_index( + foreign->referenced_table, NULL, + foreign->referenced_col_names, + foreign->n_fields, + foreign->foreign_index, + TRUE, FALSE); + } + } else { + foreign->foreign_table = + dict_sys.load_table( + {foreign->foreign_table_name_lookup, + strlen(foreign-> + foreign_table_name_lookup)}); + + if (foreign->foreign_table) { + foreign->foreign_index = + dict_foreign_find_index( + foreign->foreign_table, NULL, + foreign->foreign_col_names, + foreign->n_fields, + foreign->referenced_index, + TRUE, FALSE); + } + } + dict_sys.unlock(); + } + + if ( !((referenced) ? + foreign->referenced_table : foreign->foreign_table)) { + WSREP_WARN("FK: %s missing in query: %s", + (!foreign->referenced_table) ? + "referenced table" : "foreign table", + wsrep_thd_query(thd)); + return DB_ERROR; + } + + byte key[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; + ulint len = WSREP_MAX_SUPPORTED_KEY_LENGTH; + + dict_index_t *idx_target = (referenced) ? + foreign->referenced_index : index; + dict_index_t *idx = (referenced) ? + UT_LIST_GET_FIRST(foreign->referenced_table->indexes) : + UT_LIST_GET_FIRST(foreign->foreign_table->indexes); + int i = 0; + + while (idx != NULL && idx != idx_target) { + if (innobase_strcasecmp (idx->name, innobase_index_reserve_name) != 0) { + i++; + } + idx = UT_LIST_GET_NEXT(indexes, idx); + } + + ut_a(idx); + key[0] = byte(i); + + rcode = wsrep_rec_get_foreign_key( + &key[1], &len, rec, index, idx, + wsrep_protocol_version > 1); + + if (rcode != DB_SUCCESS) { + WSREP_ERROR( + "FK key set failed: " ULINTPF + " (" ULINTPF "%s), index: %s %s, %s", + rcode, referenced, wsrep_key_type_to_str(key_type), + (index) ? index->name() : "void index", + (index && index->table) ? index->table->name.m_name : + "void table", + wsrep_thd_query(thd)); + return DB_ERROR; + } + + strncpy(cache_key, + (wsrep_protocol_version > 1) ? + ((referenced) ? + foreign->referenced_table->name.m_name : + foreign->foreign_table->name.m_name) : + foreign->foreign_table->name.m_name, sizeof(cache_key) - 1); + cache_key_len = strlen(cache_key); + +#ifdef WSREP_DEBUG_PRINT + ulint j; + fprintf(stderr, "FK parent key, table: %s %s len: %lu ", + cache_key, wsrep_key_type_to_str(key_type), len+1); + for (j=0; j<len+1; j++) { + fprintf(stderr, " %hhX, ", key[j]); + } + fprintf(stderr, "\n"); +#endif + char *p = strchr(cache_key, '/'); + + if (p) { + *p = '\0'; + } else { + WSREP_WARN("unexpected foreign key table %s %s", + foreign->referenced_table->name.m_name, + foreign->foreign_table->name.m_name); + } + + wsrep_buf_t wkey_part[3]; + wsrep_key_t wkey = {wkey_part, 3}; + + if (!wsrep_prepare_key_for_innodb( + thd, + (const uchar*)cache_key, + cache_key_len + 1, + (const uchar*)key, len+1, + wkey_part, + (size_t*)&wkey.key_parts_num)) { + WSREP_WARN("key prepare failed for cascaded FK: %s", + wsrep_thd_query(thd)); + return DB_ERROR; + } + + rcode = wsrep_thd_append_key(thd, &wkey, 1, key_type); + + if (rcode) { + WSREP_ERROR("Appending cascaded fk row key failed: %s, " + ULINTPF, + wsrep_thd_query(thd), + rcode); + return DB_ERROR; + } + + if (pa_disable) { + wsrep_thd_set_PA_unsafe(trx->mysql_thd); + } + + return DB_SUCCESS; +} + +static int +wsrep_append_key( +/*=============*/ + THD *thd, + trx_t *trx, + TABLE_SHARE *table_share, + const char* key, + uint16_t key_len, + Wsrep_service_key_type key_type /*!< in: access type of this key + (shared, exclusive, semi...) */ +) +{ + ut_ad(!trx->is_bulk_insert()); + + DBUG_ENTER("wsrep_append_key"); + DBUG_PRINT("enter", + ("thd: %lu trx: %lld", thd_get_thread_id(thd), + (long long)trx->id)); +#ifdef WSREP_DEBUG_PRINT + fprintf(stderr, "%s conn %lu, trx " TRX_ID_FMT ", keylen %d, key %s.%s\n", + wsrep_key_type_to_str(key_type), + thd_get_thread_id(thd), trx->id, key_len, + table_share->table_name.str, key); + for (int i=0; i<key_len; i++) { + fprintf(stderr, "%hhX, ", key[i]); + } + fprintf(stderr, "\n"); +#endif + wsrep_buf_t wkey_part[3]; + wsrep_key_t wkey = {wkey_part, 3}; + + if (!wsrep_prepare_key_for_innodb( + thd, + (const uchar*)table_share->table_cache_key.str, + table_share->table_cache_key.length, + (const uchar*)key, key_len, + wkey_part, + (size_t*)&wkey.key_parts_num)) { + WSREP_WARN("key prepare failed for: %s", + (wsrep_thd_query(thd)) ? + wsrep_thd_query(thd) : "void"); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } + + int rcode = wsrep_thd_append_key(thd, &wkey, 1, key_type); + if (rcode) { + DBUG_PRINT("wsrep", ("row key failed: %d", rcode)); + WSREP_WARN("Appending row key failed: %s, %d", + (wsrep_thd_query(thd)) ? + wsrep_thd_query(thd) : "void", rcode); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } + + DBUG_RETURN(0); +} + +static bool +referenced_by_foreign_key2( +/*=======================*/ + dict_table_t* table, + dict_index_t* index) +{ + ut_ad(table != NULL); + ut_ad(index != NULL); + + const dict_foreign_set* fks = &table->referenced_set; + + for (dict_foreign_set::const_iterator it = fks->begin(); + it != fks->end(); + ++it) { + dict_foreign_t* foreign = *it; + + if (foreign->referenced_index != index) { + continue; + } + ut_ad(table == foreign->referenced_table); + return true; + } + return false; +} + +int +ha_innobase::wsrep_append_keys( +/*===========================*/ + THD *thd, + Wsrep_service_key_type key_type, /*!< in: access type of this row + operation: + (shared, exclusive, reference...) */ + const uchar* record0, /* in: row in MySQL format */ + const uchar* record1) /* in: row in MySQL format */ +{ + /* Sanity check: newly inserted records should always be passed with + EXCLUSIVE key type, all the rest are expected to carry a pre-image + */ + ut_a(record1 != NULL || key_type == WSREP_SERVICE_KEY_EXCLUSIVE); + + int rcode; + DBUG_ENTER("wsrep_append_keys"); + + bool key_appended = false; + trx_t *trx = thd_to_trx(thd); + +#ifdef WSREP_DEBUG_PRINT + fprintf(stderr, "%s conn %lu, trx " TRX_ID_FMT ", table %s\nSQL: %s\n", + wsrep_key_type_to_str(key_type), + thd_get_thread_id(thd), trx->id, + table_share->table_name.str, wsrep_thd_query(thd)); +#endif + + if (table_share && table_share->tmp_table != NO_TMP_TABLE) { + WSREP_DEBUG("skipping tmp table DML: THD: %lu tmp: %d SQL: %s", + thd_get_thread_id(thd), + table_share->tmp_table, + (wsrep_thd_query(thd)) ? + wsrep_thd_query(thd) : "void"); + DBUG_RETURN(0); + } + + if (wsrep_protocol_version == 0) { + char keyval[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; + char *key = &keyval[0]; + bool is_null; + + auto len = wsrep_store_key_val_for_row( + thd, table, 0, key, WSREP_MAX_SUPPORTED_KEY_LENGTH, + record0, &is_null); + + if (!is_null) { + rcode = wsrep_append_key( + thd, trx, table_share, keyval, + len, key_type); + + if (rcode) { + DBUG_RETURN(rcode); + } + } else { + WSREP_DEBUG("NULL key skipped (proto 0): %s", + wsrep_thd_query(thd)); + } + } else { + ut_a(table->s->keys <= 256); + uint i; + bool hasPK= false; + + for (i=0; i<table->s->keys; ++i) { + KEY* key_info = table->key_info + i; + if (key_info->flags & HA_NOSAME) { + hasPK = true; + break; + } + } + + for (i=0; i<table->s->keys; ++i) { + KEY* key_info = table->key_info + i; + + dict_index_t* idx = innobase_get_index(i); + dict_table_t* tab = (idx) ? idx->table : NULL; + + /* keyval[] shall contain an ordinal number at byte 0 + and the actual key data shall be written at byte 1. + Hence the total data length is the key length + 1 */ + char keyval0[WSREP_MAX_SUPPORTED_KEY_LENGTH+1]= {'\0'}; + char keyval1[WSREP_MAX_SUPPORTED_KEY_LENGTH+1]= {'\0'}; + keyval0[0] = (char)i; + keyval1[0] = (char)i; + char* key0 = &keyval0[1]; + char* key1 = &keyval1[1]; + + if (!tab) { + WSREP_WARN("MariaDB-InnoDB key mismatch %s %s", + table->s->table_name.str, + key_info->name.str); + } + /* !hasPK == table with no PK, + must append all non-unique keys */ + if (!hasPK || key_info->flags & HA_NOSAME || + ((tab && + referenced_by_foreign_key2(tab, idx)) || + (!tab && referenced_by_foreign_key()))) { + + bool is_null0; + auto len0 = wsrep_store_key_val_for_row( + thd, table, i, key0, + WSREP_MAX_SUPPORTED_KEY_LENGTH, + record0, &is_null0); + + if (record1) { + bool is_null1; + auto len1= wsrep_store_key_val_for_row( + thd, table, i, key1, + WSREP_MAX_SUPPORTED_KEY_LENGTH, + record1, &is_null1); + + if (is_null0 != is_null1 || + len0 != len1 || + memcmp(key0, key1, len0)) { + /* This key has chaged. If it + is unique, this is an exclusive + operation -> upgrade key type */ + if (key_info->flags & HA_NOSAME) { + key_type = WSREP_SERVICE_KEY_EXCLUSIVE; + } + + if (!is_null1) { + rcode = wsrep_append_key( + thd, trx, table_share, + keyval1, + /* for len1+1 see keyval1 + initialization comment */ + uint16_t(len1+1), + key_type); + if (rcode) + DBUG_RETURN(rcode); + } + } + } + + if (!is_null0) { + rcode = wsrep_append_key( + thd, trx, table_share, + /* for len0+1 see keyval0 + initialization comment */ + keyval0, uint16_t(len0+1), + key_type); + if (rcode) + DBUG_RETURN(rcode); + + if (key_info->flags & HA_NOSAME || + key_type == WSREP_SERVICE_KEY_SHARED|| + key_type == WSREP_SERVICE_KEY_REFERENCE) + key_appended = true; + } else { + WSREP_DEBUG("NULL key skipped: %s", + wsrep_thd_query(thd)); + } + } + } + } + + /* if no PK, calculate hash of full row, to be the key value */ + if (!key_appended && wsrep_certify_nonPK) { + uchar digest[16]; + + wsrep_calc_row_hash(digest, record0, table, m_prebuilt); + + if (int rcode = wsrep_append_key(thd, trx, table_share, + reinterpret_cast<char*> + (digest), 16, key_type)) { + DBUG_RETURN(rcode); + } + + if (record1) { + wsrep_calc_row_hash( + digest, record1, table, m_prebuilt); + if (int rcode = wsrep_append_key( + thd, trx, table_share, + reinterpret_cast<char*>(digest), 16, + key_type)) { + DBUG_RETURN(rcode); + } + } + DBUG_RETURN(0); + } + + DBUG_RETURN(0); +} +#endif /* WITH_WSREP */ + +/*********************************************************************//** +Stores a reference to the current row to 'ref' field of the handle. Note +that in the case where we have generated the clustered index for the +table, the function parameter is illogical: we MUST ASSUME that 'record' +is the current 'position' of the handle, because if row ref is actually +the row id internally generated in InnoDB, then 'record' does not contain +it. We just guess that the row id must be for the record where the handle +was positioned the last time. */ + +void +ha_innobase::position( +/*==================*/ + const uchar* record) /*!< in: row in MySQL format */ +{ + uint len; + + ut_a(m_prebuilt->trx == thd_to_trx(ha_thd())); + + if (m_prebuilt->clust_index_was_generated) { + /* No primary key was defined for the table and we + generated the clustered index from row id: the + row reference will be the row id, not any key value + that MySQL knows of */ + + len = DATA_ROW_ID_LEN; + + memcpy(ref, m_prebuilt->row_id, len); + } else { + + /* Copy primary key as the row reference */ + KEY* key_info = table->key_info + m_primary_key; + key_copy(ref, (uchar*)record, key_info, key_info->key_length); + len = key_info->key_length; + } + + ut_ad(len == ref_length); +} + +/*****************************************************************//** +Check whether there exist a column named as "FTS_DOC_ID", which is +reserved for InnoDB FTS Doc ID +@return true if there exist a "FTS_DOC_ID" column */ +static +bool +create_table_check_doc_id_col( +/*==========================*/ + trx_t* trx, /*!< in: InnoDB transaction handle */ + const TABLE* form, /*!< in: information on table + columns and indexes */ + ulint* doc_id_col) /*!< out: Doc ID column number if + there exist a FTS_DOC_ID column, + ULINT_UNDEFINED if column is of the + wrong type/name/size */ +{ + for (ulint i = 0; i < form->s->fields; i++) { + const Field* field = form->field[i]; + if (!field->stored_in_db()) { + continue; + } + + unsigned unsigned_type; + + auto col_type = get_innobase_type_from_mysql_type( + &unsigned_type, field); + + auto col_len = field->pack_length(); + + if (innobase_strcasecmp(field->field_name.str, + FTS_DOC_ID_COL_NAME) == 0) { + + /* Note the name is case sensitive due to + our internal query parser */ + if (col_type == DATA_INT + && !field->real_maybe_null() + && col_len == sizeof(doc_id_t) + && (strcmp(field->field_name.str, + FTS_DOC_ID_COL_NAME) == 0)) { + *doc_id_col = i; + } else { + push_warning_printf( + trx->mysql_thd, + Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: FTS_DOC_ID column must be" + " of BIGINT NOT NULL type, and named" + " in all capitalized characters"); + my_error(ER_WRONG_COLUMN_NAME, MYF(0), + field->field_name.str); + *doc_id_col = ULINT_UNDEFINED; + } + + return(true); + } + } + + return(false); +} + + +/** Finds all base columns needed to compute a given generated column. +This is returned as a bitmap, in field->table->tmp_set. +Works for both dict_v_col_t and dict_s_col_t columns. +@param[in] table InnoDB table +@param[in] field MySQL field +@param[in,out] col virtual or stored column */ +template <typename T> +void +prepare_vcol_for_base_setup( +/*========================*/ + const dict_table_t* table, + const Field* field, + T* col) +{ + ut_ad(col->num_base == 0); + ut_ad(col->base_col == NULL); + + MY_BITMAP *old_read_set = field->table->read_set; + + field->table->read_set = &field->table->tmp_set; + + bitmap_clear_all(&field->table->tmp_set); + field->vcol_info->expr->walk( + &Item::register_field_in_read_map, 1, field->table); + col->num_base= bitmap_bits_set(&field->table->tmp_set) + & dict_index_t::MAX_N_FIELDS; + if (col->num_base != 0) { + col->base_col = static_cast<dict_col_t**>(mem_heap_zalloc( + table->heap, col->num_base * sizeof( + * col->base_col))); + } + field->table->read_set= old_read_set; +} + + +/** Set up base columns for virtual column +@param[in] table InnoDB table +@param[in] field MySQL field +@param[in,out] v_col virtual column */ +void +innodb_base_col_setup( + dict_table_t* table, + const Field* field, + dict_v_col_t* v_col) +{ + uint16_t n = 0; + + prepare_vcol_for_base_setup(table, field, v_col); + + for (uint i= 0; i < field->table->s->fields; ++i) { + const Field* base_field = field->table->field[i]; + if (base_field->stored_in_db() + && bitmap_is_set(&field->table->tmp_set, i)) { + ulint z; + + for (z = 0; z < table->n_cols; z++) { + const char* name = dict_table_get_col_name(table, z); + if (!innobase_strcasecmp(name, + base_field->field_name.str)) { + break; + } + } + + ut_ad(z != table->n_cols); + + v_col->base_col[n] = dict_table_get_nth_col(table, z); + ut_ad(v_col->base_col[n]->ind == z); + n++; + } + } + v_col->num_base= n & dict_index_t::MAX_N_FIELDS; +} + +/** Set up base columns for stored column +@param[in] table InnoDB table +@param[in] field MySQL field +@param[in,out] s_col stored column */ +void +innodb_base_col_setup_for_stored( + const dict_table_t* table, + const Field* field, + dict_s_col_t* s_col) +{ + ulint n = 0; + + prepare_vcol_for_base_setup(table, field, s_col); + + for (uint i= 0; i < field->table->s->fields; ++i) { + const Field* base_field = field->table->field[i]; + + if (base_field->stored_in_db() + && bitmap_is_set(&field->table->tmp_set, i)) { + ulint z; + for (z = 0; z < table->n_cols; z++) { + const char* name = dict_table_get_col_name( + table, z); + if (!innobase_strcasecmp( + name, base_field->field_name.str)) { + break; + } + } + + ut_ad(z != table->n_cols); + + s_col->base_col[n] = dict_table_get_nth_col(table, z); + n++; + + if (n == s_col->num_base) { + break; + } + } + } + s_col->num_base= n; +} + +/** Create a table definition to an InnoDB database. +@return ER_* level error */ +inline MY_ATTRIBUTE((warn_unused_result)) +int +create_table_info_t::create_table_def() +{ + dict_table_t* table; + ulint nulls_allowed; + unsigned unsigned_type; + ulint binary_type; + ulint long_true_varchar; + ulint charset_no; + ulint doc_id_col = 0; + ibool has_doc_id_col = FALSE; + mem_heap_t* heap; + ha_table_option_struct *options= m_form->s->option_struct; + dberr_t err = DB_SUCCESS; + + DBUG_ENTER("create_table_def"); + DBUG_PRINT("enter", ("table_name: %s", m_table_name)); + + DBUG_ASSERT(m_trx->mysql_thd == m_thd); + + /* MySQL does the name length check. But we do additional check + on the name length here */ + const size_t table_name_len = strlen(m_table_name); + if (table_name_len > MAX_FULL_NAME_LEN) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_TABLE_NAME, + "InnoDB: Table Name or Database Name is too long"); + + DBUG_RETURN(ER_TABLE_NAME); + } + + if (m_table_name[table_name_len - 1] == '/') { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_TABLE_NAME, + "InnoDB: Table name is empty"); + + DBUG_RETURN(ER_WRONG_TABLE_NAME); + } + + /* Find out the number of virtual columns. */ + ulint num_v = 0; + const bool omit_virtual = ha_innobase::omits_virtual_cols(*m_form->s); + const ulint n_cols = omit_virtual + ? m_form->s->stored_fields : m_form->s->fields; + + if (!omit_virtual) { + for (ulint i = 0; i < n_cols; i++) { + num_v += !m_form->field[i]->stored_in_db(); + } + } + + /* Check whether there already exists a FTS_DOC_ID column */ + if (create_table_check_doc_id_col(m_trx, m_form, &doc_id_col)){ + + /* Raise error if the Doc ID column is of wrong type or name */ + if (doc_id_col == ULINT_UNDEFINED) { + DBUG_RETURN(HA_ERR_GENERIC); + } else { + has_doc_id_col = TRUE; + } + } + + /* Adjust the number of columns for the FTS hidden field */ + const ulint actual_n_cols = n_cols + + (m_flags2 & DICT_TF2_FTS && !has_doc_id_col); + + table = dict_table_t::create({m_table_name,table_name_len}, nullptr, + actual_n_cols, num_v, m_flags, m_flags2); + + /* Set the hidden doc_id column. */ + if (m_flags2 & DICT_TF2_FTS) { + table->fts->doc_col = has_doc_id_col + ? doc_id_col : n_cols - num_v; + } + + if (DICT_TF_HAS_DATA_DIR(m_flags)) { + ut_a(strlen(m_remote_path)); + + table->data_dir_path = mem_heap_strdup( + table->heap, m_remote_path); + + } else { + table->data_dir_path = NULL; + } + + heap = mem_heap_create(1000); + auto _ = make_scope_exit([heap]() { mem_heap_free(heap); }); + + ut_d(bool have_vers_start = false); + ut_d(bool have_vers_end = false); + + for (ulint i = 0, j = 0; j < n_cols; i++) { + Field* field = m_form->field[i]; + ulint vers_row = 0; + + if (m_form->versioned()) { + if (i == m_form->s->vers.start_fieldno) { + vers_row = DATA_VERS_START; + ut_d(have_vers_start = true); + } else if (i == m_form->s->vers.end_fieldno) { + vers_row = DATA_VERS_END; + ut_d(have_vers_end = true); + } else if (!(field->flags + & VERS_UPDATE_UNVERSIONED_FLAG)) { + vers_row = DATA_VERSIONED; + } + } + + auto col_type = get_innobase_type_from_mysql_type( + &unsigned_type, field); + + if (!col_type) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_CREATE_TABLE, + "Error creating table '%s' with" + " column '%s'. Please check its" + " column type and try to re-create" + " the table with an appropriate" + " column type.", + table->name.m_name, field->field_name.str); +err_col: + dict_mem_table_free(table); + DBUG_RETURN(HA_ERR_GENERIC); + } + + nulls_allowed = field->real_maybe_null() ? 0 : DATA_NOT_NULL; + binary_type = field->binary() ? DATA_BINARY_TYPE : 0; + + charset_no = 0; + + if (dtype_is_string_type(col_type)) { + + charset_no = (ulint) field->charset()->number; + + DBUG_EXECUTE_IF("simulate_max_char_col", + charset_no = MAX_CHAR_COLL_NUM + 1; + ); + + if (charset_no > MAX_CHAR_COLL_NUM) { + /* in data0type.h we assume that the + number fits in one byte in prtype */ + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_CREATE_TABLE, + "In InnoDB, charset-collation codes" + " must be below 256." + " Unsupported code " ULINTPF ".", + charset_no); + dict_mem_table_free(table); + + DBUG_RETURN(ER_CANT_CREATE_TABLE); + } + } + + auto col_len = field->pack_length(); + + /* The MySQL pack length contains 1 or 2 bytes length field + for a true VARCHAR. Let us subtract that, so that the InnoDB + column length in the InnoDB data dictionary is the real + maximum byte length of the actual data. */ + + long_true_varchar = 0; + + if (field->type() == MYSQL_TYPE_VARCHAR) { + col_len -= ((Field_varstring*) field)->length_bytes; + + if (((Field_varstring*) field)->length_bytes == 2) { + long_true_varchar = DATA_LONG_TRUE_VARCHAR; + } + } + + /* First check whether the column to be added has a + system reserved name. */ + if (dict_col_name_is_reserved(field->field_name.str)){ + my_error(ER_WRONG_COLUMN_NAME, MYF(0), + field->field_name.str); + goto err_col; + } + + ulint is_virtual = !field->stored_in_db() ? DATA_VIRTUAL : 0; + + if (!is_virtual) { + dict_mem_table_add_col(table, heap, + field->field_name.str, col_type, + dtype_form_prtype( + (ulint) field->type() + | nulls_allowed | unsigned_type + | binary_type | long_true_varchar + | vers_row, + charset_no), + col_len); + } else if (!omit_virtual) { + dict_mem_table_add_v_col(table, heap, + field->field_name.str, col_type, + dtype_form_prtype( + (ulint) field->type() + | nulls_allowed | unsigned_type + | binary_type | long_true_varchar + | vers_row + | is_virtual, + charset_no), + col_len, i, 0); + } + + if (innobase_is_s_fld(field)) { + ut_ad(!is_virtual); + /* Added stored column in m_s_cols list. */ + dict_mem_table_add_s_col( + table, 0); + } + + if (is_virtual && omit_virtual) { + continue; + } + + j++; + } + + ut_ad(have_vers_start == have_vers_end); + ut_ad(table->versioned() == have_vers_start); + ut_ad(!table->versioned() || table->vers_start != table->vers_end); + + if (num_v) { + for (ulint i = 0, j = 0; i < n_cols; i++) { + dict_v_col_t* v_col; + + const Field* field = m_form->field[i]; + + if (field->stored_in_db()) { + continue; + } + + v_col = dict_table_get_nth_v_col(table, j); + + j++; + + innodb_base_col_setup(table, field, v_col); + } + } + + /** Fill base columns for the stored column present in the list. */ + if (table->s_cols && !table->s_cols->empty()) { + for (ulint i = 0; i < n_cols; i++) { + Field* field = m_form->field[i]; + + if (!innobase_is_s_fld(field)) { + continue; + } + + dict_s_col_list::iterator it; + for (it = table->s_cols->begin(); + it != table->s_cols->end(); ++it) { + dict_s_col_t s_col = *it; + + if (s_col.s_pos == i) { + innodb_base_col_setup_for_stored( + table, field, &s_col); + break; + } + } + } + } + + /* Add the FTS doc_id hidden column. */ + if (m_flags2 & DICT_TF2_FTS && !has_doc_id_col) { + fts_add_doc_id_column(table, heap); + } + + dict_table_add_system_columns(table, heap); + + if (table->is_temporary()) { + if ((options->encryption == 1 + && !innodb_encrypt_temporary_tables) + || (options->encryption == 2 + && innodb_encrypt_temporary_tables)) { + push_warning_printf(m_thd, + Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "Ignoring encryption parameter during " + "temporary table creation."); + } + + table->id = dict_sys.acquire_temporary_table_id(); + ut_ad(dict_tf_get_rec_format(table->flags) + != REC_FORMAT_COMPRESSED); + table->space_id = SRV_TMP_SPACE_ID; + table->space = fil_system.temp_space; + table->add_to_cache(); + } else { + ut_ad(dict_sys.sys_tables_exist()); + + err = row_create_table_for_mysql(table, m_trx); + } + + switch (err) { + case DB_SUCCESS: + ut_ad(table); + m_table = table; + DBUG_RETURN(0); + default: + break; + case DB_DUPLICATE_KEY: + char display_name[FN_REFLEN]; + char* buf_end = innobase_convert_identifier( + display_name, sizeof(display_name) - 1, + m_table_name, strlen(m_table_name), + m_thd); + + *buf_end = '\0'; + + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), display_name); + } + + DBUG_RETURN(convert_error_code_to_mysql(err, m_flags, m_thd)); +} + +/*****************************************************************//** +Creates an index in an InnoDB database. */ +inline +int +create_index( +/*=========*/ + trx_t* trx, /*!< in: InnoDB transaction handle */ + const TABLE* form, /*!< in: information on table + columns and indexes */ + dict_table_t* table, /*!< in,out: table */ + uint key_num) /*!< in: index number */ +{ + dict_index_t* index; + int error; + const KEY* key; + ulint* field_lengths; + + DBUG_ENTER("create_index"); + + key = form->key_info + key_num; + + /* Assert that "GEN_CLUST_INDEX" cannot be used as non-primary index */ + ut_a(innobase_strcasecmp(key->name.str, innobase_index_reserve_name) != 0); + const ha_table_option_struct& o = *form->s->option_struct; + + if (key->flags & (HA_SPATIAL | HA_FULLTEXT)) { + /* Only one of these can be specified at a time. */ + ut_ad(~key->flags & (HA_SPATIAL | HA_FULLTEXT)); + ut_ad(!(key->flags & HA_NOSAME)); + index = dict_mem_index_create(table, key->name.str, + (key->flags & HA_SPATIAL) + ? DICT_SPATIAL : DICT_FTS, + key->user_defined_key_parts); + + for (ulint i = 0; i < key->user_defined_key_parts; i++) { + const Field* field = key->key_part[i].field; + + /* We do not support special (Fulltext or Spatial) + index on virtual columns */ + if (!field->stored_in_db()) { + ut_ad(0); + DBUG_RETURN(HA_ERR_UNSUPPORTED); + } + + dict_mem_index_add_field(index, field->field_name.str, + 0, + key->key_part->key_part_flag + & HA_REVERSE_SORT); + } + + DBUG_RETURN(convert_error_code_to_mysql( + row_create_index_for_mysql( + index, trx, NULL, + fil_encryption_t(o.encryption), + uint32_t(o.encryption_key_id)), + table->flags, NULL)); + } + + ulint ind_type = 0; + + if (key_num == form->s->primary_key) { + ind_type |= DICT_CLUSTERED; + } + + if (key->flags & HA_NOSAME) { + ind_type |= DICT_UNIQUE; + } + + field_lengths = (ulint*) my_malloc(PSI_INSTRUMENT_ME, + key->user_defined_key_parts * sizeof * + field_lengths, MYF(MY_FAE)); + + /* We pass 0 as the space id, and determine at a lower level the space + id where to store the table */ + + index = dict_mem_index_create(table, key->name.str, + ind_type, key->user_defined_key_parts); + + for (ulint i = 0; i < key->user_defined_key_parts; i++) { + KEY_PART_INFO* key_part = key->key_part + i; + ulint prefix_len; + unsigned is_unsigned; + + + /* (The flag HA_PART_KEY_SEG denotes in MySQL a + column prefix field in an index: we only store a + specified number of first bytes of the column to + the index field.) The flag does not seem to be + properly set by MySQL. Let us fall back on testing + the length of the key part versus the column. + We first reach to the table's column; if the index is on a + prefix, key_part->field is not the table's column (it's a + "fake" field forged in open_table_from_share() with length + equal to the length of the prefix); so we have to go to + form->fied. */ + Field* field= form->field[key_part->field->field_index]; + if (field == NULL) + ut_error; + + const char* field_name = key_part->field->field_name.str; + + auto col_type = get_innobase_type_from_mysql_type( + &is_unsigned, key_part->field); + + if (DATA_LARGE_MTYPE(col_type) + || (key_part->length < field->pack_length() + && field->type() != MYSQL_TYPE_VARCHAR) + || (field->type() == MYSQL_TYPE_VARCHAR + && key_part->length < field->pack_length() + - ((Field_varstring*) field)->length_bytes)) { + + switch (col_type) { + default: + prefix_len = key_part->length; + break; + case DATA_INT: + case DATA_FLOAT: + case DATA_DOUBLE: + case DATA_DECIMAL: + sql_print_error( + "MariaDB is trying to create a column" + " prefix index field, on an" + " inappropriate data type. Table" + " name %s, column name %s.", + form->s->table_name.str, + key_part->field->field_name.str); + + prefix_len = 0; + } + } else { + prefix_len = 0; + } + + ut_ad(prefix_len % field->charset()->mbmaxlen == 0); + + field_lengths[i] = key_part->length; + + if (!key_part->field->stored_in_db()) { + index->type |= DICT_VIRTUAL; + } + + dict_mem_index_add_field(index, field_name, prefix_len, + key_part->key_part_flag + & HA_REVERSE_SORT); + } + + ut_ad(key->flags & HA_FULLTEXT || !(index->type & DICT_FTS)); + + /* Even though we've defined max_supported_key_part_length, we + still do our own checking using field_lengths to be absolutely + sure we don't create too long indexes. */ + ulint flags = table->flags; + + error = convert_error_code_to_mysql( + row_create_index_for_mysql(index, trx, field_lengths, + fil_encryption_t(o.encryption), + uint32_t(o.encryption_key_id)), + flags, NULL); + + my_free(field_lengths); + + DBUG_RETURN(error); +} + +/** Return a display name for the row format +@param[in] row_format Row Format +@return row format name */ +static +const char* +get_row_format_name( + enum row_type row_format) +{ + switch (row_format) { + case ROW_TYPE_COMPACT: + return("COMPACT"); + case ROW_TYPE_COMPRESSED: + return("COMPRESSED"); + case ROW_TYPE_DYNAMIC: + return("DYNAMIC"); + case ROW_TYPE_REDUNDANT: + return("REDUNDANT"); + case ROW_TYPE_DEFAULT: + return("DEFAULT"); + case ROW_TYPE_FIXED: + return("FIXED"); + case ROW_TYPE_PAGE: + case ROW_TYPE_NOT_USED: + break; + } + return("NOT USED"); +} + +/** Validate DATA DIRECTORY option. +@return true if valid, false if not. */ +bool +create_table_info_t::create_option_data_directory_is_valid() +{ + bool is_valid = true; + + ut_ad(m_create_info->data_file_name + && m_create_info->data_file_name[0] != '\0'); + + /* Use DATA DIRECTORY only with file-per-table. */ + if (!m_allow_file_per_table) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: DATA DIRECTORY requires" + " innodb_file_per_table."); + is_valid = false; + } + + /* Do not use DATA DIRECTORY with TEMPORARY TABLE. */ + if (m_create_info->tmp_table()) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: DATA DIRECTORY cannot be used" + " for TEMPORARY tables."); + is_valid = false; + } + + /* We check for a DATA DIRECTORY mixed with TABLESPACE in + create_option_tablespace_is_valid(), no need to here. */ + + return(is_valid); +} + +/** Validate the create options. Check that the options KEY_BLOCK_SIZE, +ROW_FORMAT, DATA DIRECTORY, TEMPORARY are compatible with +each other and other settings. These CREATE OPTIONS are not validated +here unless innodb_strict_mode is on. With strict mode, this function +will report each problem it finds using a custom message with error +code ER_ILLEGAL_HA_CREATE_OPTION, not its built-in message. +@return NULL if valid, string name of bad option if not. */ +const char* +create_table_info_t::create_options_are_invalid() +{ + bool has_key_block_size = (m_create_info->key_block_size != 0); + + const char* ret = NULL; + enum row_type row_format = m_create_info->row_type; + const bool is_temp = m_create_info->tmp_table(); + + ut_ad(m_thd != NULL); + + /* If innodb_strict_mode is not set don't do any more validation. */ + if (!THDVAR(m_thd, strict_mode)) { + return(NULL); + } + + /* Check if a non-zero KEY_BLOCK_SIZE was specified. */ + if (has_key_block_size) { + if (is_temp || innodb_read_only_compressed) { + my_error(ER_UNSUPPORTED_COMPRESSED_TABLE, MYF(0)); + return("KEY_BLOCK_SIZE"); + } + + switch (m_create_info->key_block_size) { + ulint kbs_max; + case 1: + case 2: + case 4: + case 8: + case 16: + /* The maximum KEY_BLOCK_SIZE (KBS) is + UNIV_PAGE_SIZE_MAX. But if srv_page_size is + smaller than UNIV_PAGE_SIZE_MAX, the maximum + KBS is also smaller. */ + kbs_max = ut_min( + 1U << (UNIV_PAGE_SSIZE_MAX - 1), + 1U << (PAGE_ZIP_SSIZE_MAX - 1)); + if (m_create_info->key_block_size > kbs_max) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: KEY_BLOCK_SIZE=%ld" + " cannot be larger than %ld.", + m_create_info->key_block_size, + kbs_max); + ret = "KEY_BLOCK_SIZE"; + } + + /* Valid KEY_BLOCK_SIZE, check its dependencies. */ + if (!m_allow_file_per_table) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: KEY_BLOCK_SIZE requires" + " innodb_file_per_table."); + ret = "KEY_BLOCK_SIZE"; + } + break; + default: + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: invalid KEY_BLOCK_SIZE = %u." + " Valid values are [1, 2, 4, 8, 16]", + (uint) m_create_info->key_block_size); + ret = "KEY_BLOCK_SIZE"; + break; + } + } + + /* Check for a valid InnoDB ROW_FORMAT specifier and + other incompatibilities. */ + switch (row_format) { + case ROW_TYPE_COMPRESSED: + if (is_temp || innodb_read_only_compressed) { + my_error(ER_UNSUPPORTED_COMPRESSED_TABLE, MYF(0)); + return("ROW_FORMAT"); + } + if (!m_allow_file_per_table) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ROW_FORMAT=%s requires" + " innodb_file_per_table.", + get_row_format_name(row_format)); + ret = "ROW_FORMAT"; + } + break; + case ROW_TYPE_DYNAMIC: + case ROW_TYPE_COMPACT: + case ROW_TYPE_REDUNDANT: + if (has_key_block_size) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: cannot specify ROW_FORMAT = %s" + " with KEY_BLOCK_SIZE.", + get_row_format_name(row_format)); + ret = "KEY_BLOCK_SIZE"; + } + break; + case ROW_TYPE_DEFAULT: + break; + case ROW_TYPE_FIXED: + case ROW_TYPE_PAGE: + case ROW_TYPE_NOT_USED: + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: invalid ROW_FORMAT specifier."); + ret = "ROW_TYPE"; + break; + } + + if (!m_create_info->data_file_name + || !m_create_info->data_file_name[0]) { + } else if (!my_use_symdir) { + my_error(WARN_OPTION_IGNORED, MYF(ME_WARNING), + "DATA DIRECTORY"); + } else if (!create_option_data_directory_is_valid()) { + ret = "DATA DIRECTORY"; + } + + /* Do not allow INDEX_DIRECTORY */ + if (m_create_info->index_file_name) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: INDEX DIRECTORY is not supported"); + ret = "INDEX DIRECTORY"; + } + + /* Don't support compressed table when page size > 16k. */ + if ((has_key_block_size || row_format == ROW_TYPE_COMPRESSED) + && srv_page_size > UNIV_PAGE_SIZE_DEF) { + push_warning(m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: Cannot create a COMPRESSED table" + " when innodb_page_size > 16k."); + + if (has_key_block_size) { + ret = "KEY_BLOCK_SIZE"; + } else { + ret = "ROW_TYPE"; + } + } + + return(ret); +} + +/*****************************************************************//** +Check engine specific table options not handled by SQL-parser. +@return NULL if valid, string if not */ +const char* +create_table_info_t::check_table_options() +{ + enum row_type row_format = m_create_info->row_type; + const ha_table_option_struct *options= m_form->s->option_struct; + + switch (options->encryption) { + case FIL_ENCRYPTION_OFF: + if (options->encryption_key_id != FIL_DEFAULT_ENCRYPTION_KEY) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: ENCRYPTED=NO implies" + " ENCRYPTION_KEY_ID=1"); + compile_time_assert(FIL_DEFAULT_ENCRYPTION_KEY == 1); + } + if (srv_encrypt_tables != 2) { + break; + } + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: ENCRYPTED=NO cannot be used with" + " innodb_encrypt_tables=FORCE"); + return "ENCRYPTED"; + case FIL_ENCRYPTION_DEFAULT: + if (!srv_encrypt_tables) { + break; + } + /* fall through */ + case FIL_ENCRYPTION_ON: + const uint32_t key_id = uint32_t(options->encryption_key_id); + if (!encryption_key_id_exists(key_id)) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: ENCRYPTION_KEY_ID %u not available", + key_id); + return "ENCRYPTION_KEY_ID"; + } + + /* We do not support encryption for spatial indexes, + except if innodb_checksum_algorithm=full_crc32. + Do not allow ENCRYPTED=YES if any SPATIAL INDEX exists. */ + if (options->encryption != FIL_ENCRYPTION_ON + || srv_checksum_algorithm + >= SRV_CHECKSUM_ALGORITHM_FULL_CRC32) { + break; + } + for (ulint i = 0; i < m_form->s->keys; i++) { + if (m_form->key_info[i].flags & HA_SPATIAL) { + push_warning(m_thd, + Sql_condition::WARN_LEVEL_WARN, + HA_ERR_UNSUPPORTED, + "InnoDB: ENCRYPTED=YES is not" + " supported for SPATIAL INDEX"); + return "ENCRYPTED"; + } + } + } + + if (!m_allow_file_per_table + && options->encryption != FIL_ENCRYPTION_DEFAULT) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: ENCRYPTED requires innodb_file_per_table"); + return "ENCRYPTED"; + } + + /* Check page compression requirements */ + if (options->page_compressed) { + + if (row_format == ROW_TYPE_COMPRESSED) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: PAGE_COMPRESSED table can't have" + " ROW_TYPE=COMPRESSED"); + return "PAGE_COMPRESSED"; + } + + switch (row_format) { + default: + break; + case ROW_TYPE_DEFAULT: + if (m_default_row_format + != DEFAULT_ROW_FORMAT_REDUNDANT) { + break; + } + /* fall through */ + case ROW_TYPE_REDUNDANT: + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: PAGE_COMPRESSED table can't have" + " ROW_TYPE=REDUNDANT"); + return "PAGE_COMPRESSED"; + } + + if (!m_allow_file_per_table) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: PAGE_COMPRESSED requires" + " innodb_file_per_table."); + return "PAGE_COMPRESSED"; + } + + if (m_create_info->key_block_size) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: PAGE_COMPRESSED table can't have" + " key_block_size"); + return "PAGE_COMPRESSED"; + } + } + + /* Check page compression level requirements, some of them are + already checked above */ + if (options->page_compression_level != 0) { + if (options->page_compressed == false) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: PAGE_COMPRESSION_LEVEL requires" + " PAGE_COMPRESSED"); + return "PAGE_COMPRESSION_LEVEL"; + } + + if (options->page_compression_level < 1 || options->page_compression_level > 9) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_WRONG_CREATE_OPTION, + "InnoDB: invalid PAGE_COMPRESSION_LEVEL = %lu." + " Valid values are [1, 2, 3, 4, 5, 6, 7, 8, 9]", + options->page_compression_level); + return "PAGE_COMPRESSION_LEVEL"; + } + } + + return NULL; +} + +/*****************************************************************//** +Update create_info. Used in SHOW CREATE TABLE et al. */ + +void +ha_innobase::update_create_info( +/*============================*/ + HA_CREATE_INFO* create_info) /*!< in/out: create info */ +{ + if (!(create_info->used_fields & HA_CREATE_USED_AUTO)) { + info(HA_STATUS_AUTO); + create_info->auto_increment_value = stats.auto_increment_value; + } + + if (m_prebuilt->table->is_temporary()) { + return; + } + + dict_get_and_save_data_dir_path(m_prebuilt->table); + + if (m_prebuilt->table->data_dir_path) { + create_info->data_file_name = m_prebuilt->table->data_dir_path; + } +} + +/*****************************************************************//** +Initialize the table FTS stopword list +@return TRUE if success */ +ibool +innobase_fts_load_stopword( +/*=======================*/ + dict_table_t* table, /*!< in: Table has the FTS */ + trx_t* trx, /*!< in: transaction */ + THD* thd) /*!< in: current thread */ +{ + ut_ad(dict_sys.locked()); + + const char *stopword_table= THDVAR(thd, ft_user_stopword_table); + if (!stopword_table) + { + mysql_mutex_lock(&LOCK_global_system_variables); + if (innobase_server_stopword_table) + stopword_table= thd_strdup(thd, innobase_server_stopword_table); + mysql_mutex_unlock(&LOCK_global_system_variables); + } + + table->fts->dict_locked= true; + bool success= fts_load_stopword(table, trx, stopword_table, + THDVAR(thd, ft_enable_stopword), false); + table->fts->dict_locked= false; + return success; +} + +/** Parse the table name into normal name and remote path if needed. +@param[in] name Table name (db/table or full path). +@return 0 if successful, otherwise, error number */ +int +create_table_info_t::parse_table_name( + const char* +#ifdef _WIN32 + name +#endif + ) +{ + DBUG_ENTER("parse_table_name"); + +#ifdef _WIN32 + /* Names passed in from server are in two formats: + 1. <database_name>/<table_name>: for normal table creation + 2. full path: for temp table creation, or DATA DIRECTORY. + + When srv_file_per_table is on and mysqld_embedded is off, + check for full path pattern, i.e. + X:\dir\..., X is a driver letter, or + \\dir1\dir2\..., UNC path + returns error if it is in full path format, but not creating a temp. + table. Currently InnoDB does not support symbolic link on Windows. */ + + if (m_innodb_file_per_table + && !mysqld_embedded + && !m_create_info->tmp_table()) { + + if ((name[1] == ':') + || (name[0] == '\\' && name[1] == '\\')) { + sql_print_error("Cannot create table %s\n", name); + DBUG_RETURN(HA_ERR_GENERIC); + } + } +#endif + + m_remote_path[0] = '\0'; + + /* Make sure DATA DIRECTORY is compatible with other options + and set the remote path. In the case of either; + CREATE TEMPORARY TABLE ... DATA DIRECTORY={path} ... ; + CREATE TABLE ... DATA DIRECTORY={path} TABLESPACE={name}... ; + we ignore the DATA DIRECTORY. */ + if (m_create_info->data_file_name + && m_create_info->data_file_name[0] + && my_use_symdir) { + if (!create_option_data_directory_is_valid()) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, + ER_DEFAULT(WARN_OPTION_IGNORED), + "DATA DIRECTORY"); + + m_flags &= ~DICT_TF_MASK_DATA_DIR; + } else { + strncpy(m_remote_path, + m_create_info->data_file_name, + FN_REFLEN - 1); + } + } + + if (m_create_info->index_file_name) { + my_error(WARN_OPTION_IGNORED, ME_WARNING, + "INDEX DIRECTORY"); + } + + DBUG_RETURN(0); +} + +/** @return whether innodb_strict_mode is active */ +bool ha_innobase::is_innodb_strict_mode(THD *thd) +{ + return THDVAR(thd, strict_mode); +} + +/** Determine InnoDB table flags. +If strict_mode=OFF, this will adjust the flags to what should be assumed. +@retval true on success +@retval false on error */ +bool create_table_info_t::innobase_table_flags() +{ + DBUG_ENTER("innobase_table_flags"); + + const char* fts_doc_id_index_bad = NULL; + ulint zip_ssize = 0; + enum row_type row_type; + rec_format_t innodb_row_format = + get_row_format(m_default_row_format); + const bool is_temp = m_create_info->tmp_table(); + bool zip_allowed = !is_temp; + + const ulint zip_ssize_max = + ut_min(static_cast<ulint>(UNIV_PAGE_SSIZE_MAX), + static_cast<ulint>(PAGE_ZIP_SSIZE_MAX)); + + ha_table_option_struct *options= m_form->s->option_struct; + + m_flags = 0; + m_flags2 = 0; + + /* Check if there are any FTS indexes defined on this table. */ + const uint fts_n_uniq= m_form->versioned() ? 2 : 1; + for (uint i = 0; i < m_form->s->keys; i++) { + const KEY* key = &m_form->key_info[i]; + + if (key->flags & HA_FULLTEXT) { + m_flags2 |= DICT_TF2_FTS; + + /* We don't support FTS indexes in temporary + tables. */ + if (is_temp) { + my_error(ER_INNODB_NO_FT_TEMP_TABLE, MYF(0)); + DBUG_RETURN(false); + } + + if (fts_doc_id_index_bad) { + goto index_bad; + } + } + + if (innobase_strcasecmp(key->name.str, FTS_DOC_ID_INDEX_NAME)) { + continue; + } + + /* Do a pre-check on FTS DOC ID index */ + if (!(key->flags & HA_NOSAME) + || key->user_defined_key_parts != fts_n_uniq + || (key->key_part[0].key_part_flag & HA_REVERSE_SORT) + || strcmp(key->name.str, FTS_DOC_ID_INDEX_NAME) + || strcmp(key->key_part[0].field->field_name.str, + FTS_DOC_ID_COL_NAME)) { + fts_doc_id_index_bad = key->name.str; + } + + if (fts_doc_id_index_bad && (m_flags2 & DICT_TF2_FTS)) { +index_bad: + my_error(ER_INNODB_FT_WRONG_DOCID_INDEX, MYF(0), + fts_doc_id_index_bad); + DBUG_RETURN(false); + } + } + + if (m_create_info->key_block_size > 0) { + /* The requested compressed page size (key_block_size) + is given in kilobytes. If it is a valid number, store + that value as the number of log2 shifts from 512 in + zip_ssize. Zero means it is not compressed. */ + ulint zssize; /* Zip Shift Size */ + ulint kbsize; /* Key Block Size */ + for (zssize = kbsize = 1; + zssize <= zip_ssize_max; + zssize++, kbsize <<= 1) { + if (kbsize == m_create_info->key_block_size) { + zip_ssize = zssize; + break; + } + } + + /* Make sure compressed row format is allowed. */ + if (is_temp) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: KEY_BLOCK_SIZE is ignored" + " for TEMPORARY TABLE."); + zip_allowed = false; + } else if (!m_allow_file_per_table) { + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: KEY_BLOCK_SIZE requires" + " innodb_file_per_table."); + zip_allowed = false; + } + + if (!zip_allowed + || zssize > zip_ssize_max) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ignoring KEY_BLOCK_SIZE=%u.", + (uint) m_create_info->key_block_size); + } + } + + row_type = m_create_info->row_type; + + if (zip_ssize && zip_allowed) { + /* if ROW_FORMAT is set to default, + automatically change it to COMPRESSED. */ + if (row_type == ROW_TYPE_DEFAULT) { + row_type = ROW_TYPE_COMPRESSED; + } else if (row_type != ROW_TYPE_COMPRESSED) { + /* ROW_FORMAT other than COMPRESSED + ignores KEY_BLOCK_SIZE. It does not + make sense to reject conflicting + KEY_BLOCK_SIZE and ROW_FORMAT, because + such combinations can be obtained + with ALTER TABLE anyway. */ + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ignoring KEY_BLOCK_SIZE=%u" + " unless ROW_FORMAT=COMPRESSED.", + (uint) m_create_info->key_block_size); + zip_allowed = false; + } + } else { + /* zip_ssize == 0 means no KEY_BLOCK_SIZE. */ + if (row_type == ROW_TYPE_COMPRESSED && zip_allowed) { + /* ROW_FORMAT=COMPRESSED without KEY_BLOCK_SIZE + implies half the maximum KEY_BLOCK_SIZE(*1k) or + srv_page_size, whichever is less. */ + zip_ssize = zip_ssize_max - 1; + } + } + + /* Validate the row format. Correct it if necessary */ + + switch (row_type) { + case ROW_TYPE_REDUNDANT: + innodb_row_format = REC_FORMAT_REDUNDANT; + break; + case ROW_TYPE_COMPACT: + innodb_row_format = REC_FORMAT_COMPACT; + break; + case ROW_TYPE_COMPRESSED: + if (is_temp) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ROW_FORMAT=%s is ignored for" + " TEMPORARY TABLE.", + get_row_format_name(row_type)); + } else if (!m_allow_file_per_table) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ROW_FORMAT=COMPRESSED requires" + " innodb_file_per_table."); + } else { + innodb_row_format = REC_FORMAT_COMPRESSED; + break; + } + zip_allowed = false; + /* Set ROW_FORMAT = COMPACT */ + /* fall through */ + case ROW_TYPE_NOT_USED: + case ROW_TYPE_FIXED: + case ROW_TYPE_PAGE: + push_warning( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: assuming ROW_FORMAT=DYNAMIC."); + /* fall through */ + case ROW_TYPE_DYNAMIC: + innodb_row_format = REC_FORMAT_DYNAMIC; + break; + case ROW_TYPE_DEFAULT: + ; + } + + /* Don't support compressed table when page size > 16k. */ + if (zip_allowed && zip_ssize && srv_page_size > UNIV_PAGE_SIZE_DEF) { + push_warning(m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: Cannot create a COMPRESSED table" + " when innodb_page_size > 16k." + " Assuming ROW_FORMAT=DYNAMIC."); + zip_allowed = false; + } + + ut_ad(!is_temp || !zip_allowed); + ut_ad(!is_temp || innodb_row_format != REC_FORMAT_COMPRESSED); + + /* Set the table flags */ + if (!zip_allowed) { + zip_ssize = 0; + } + + ulint level = 0; + + if (is_temp) { + m_flags2 |= DICT_TF2_TEMPORARY; + } else { + if (m_use_file_per_table) { + m_flags2 |= DICT_TF2_USE_FILE_PER_TABLE; + } + + level = ulint(options->page_compression_level); + if (!level) { + level = page_zip_level; + if (!level && options->page_compressed) { + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: PAGE_COMPRESSED requires" + " PAGE_COMPRESSION_LEVEL or" + " innodb_compression_level > 0"); + DBUG_RETURN(false); + } + } + } + + /* Set the table flags */ + dict_tf_set(&m_flags, innodb_row_format, zip_ssize, + m_use_data_dir, level && options->page_compressed, level); + + if (m_form->s->table_type == TABLE_TYPE_SEQUENCE) { + m_flags |= DICT_TF_MASK_NO_ROLLBACK; + } + + /* Set the flags2 when create table or alter tables */ + m_flags2 |= DICT_TF2_FTS_AUX_HEX_NAME; + DBUG_EXECUTE_IF("innodb_test_wrong_fts_aux_table_name", + m_flags2 &= ~DICT_TF2_FTS_AUX_HEX_NAME;); + + DBUG_RETURN(true); +} + +/** Parse MERGE_THRESHOLD value from the string. +@param[in] thd connection +@param[in] str string which might include 'MERGE_THRESHOLD=' +@return value parsed. 0 means not found or invalid value. */ +static +unsigned +innobase_parse_merge_threshold( + THD* thd, + const char* str) +{ + static const char* label = "MERGE_THRESHOLD="; + static const size_t label_len = strlen(label); + const char* pos = str; + + pos = strstr(str, label); + + if (pos == NULL) { + return(0); + } + + pos += label_len; + + lint ret = atoi(pos); + + if (ret > 0 && ret <= 50) { + return(static_cast<unsigned>(ret)); + } + + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: Invalid value for MERGE_THRESHOLD in the CREATE TABLE" + " statement. The value is ignored."); + + return(0); +} + +/** Parse hint for table and its indexes, and update the information +in dictionary. +@param[in] thd connection +@param[in,out] table target table +@param[in] table_share table definition */ +void +innobase_parse_hint_from_comment( + THD* thd, + dict_table_t* table, + const TABLE_SHARE* table_share) +{ + unsigned merge_threshold_table; + unsigned merge_threshold_index[MAX_KEY]; + bool is_found[MAX_KEY]; + + if (table_share->comment.str != NULL) { + merge_threshold_table + = innobase_parse_merge_threshold( + thd, table_share->comment.str); + } else { + merge_threshold_table = DICT_INDEX_MERGE_THRESHOLD_DEFAULT; + } + + if (merge_threshold_table == 0) { + merge_threshold_table = DICT_INDEX_MERGE_THRESHOLD_DEFAULT; + } + + for (uint i = 0; i < table_share->keys; i++) { + KEY* key_info = &table_share->key_info[i]; + + ut_ad(i < sizeof(merge_threshold_index) + / sizeof(merge_threshold_index[0])); + + if (key_info->flags & HA_USES_COMMENT + && key_info->comment.str != NULL) { + merge_threshold_index[i] + = innobase_parse_merge_threshold( + thd, key_info->comment.str); + } else { + merge_threshold_index[i] = merge_threshold_table; + } + + if (merge_threshold_index[i] == 0) { + merge_threshold_index[i] = merge_threshold_table; + } + } + + /* update SYS_INDEX table */ + if (!table->is_temporary()) { + for (uint i = 0; i < table_share->keys; i++) { + is_found[i] = false; + } + + for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes); + index != NULL; + index = UT_LIST_GET_NEXT(indexes, index)) { + + if (dict_index_is_auto_gen_clust(index)) { + + /* GEN_CLUST_INDEX should use + merge_threshold_table */ + dict_index_set_merge_threshold( + index, merge_threshold_table); + continue; + } + + for (uint i = 0; i < table_share->keys; i++) { + if (is_found[i]) { + continue; + } + + KEY* key_info = &table_share->key_info[i]; + + if (innobase_strcasecmp( + index->name, key_info->name.str) == 0) { + + dict_index_set_merge_threshold( + index, + merge_threshold_index[i]); + is_found[i] = true; + break; + } + } + } + } + + for (uint i = 0; i < table_share->keys; i++) { + is_found[i] = false; + } + + /* update in memory */ + for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes); + index != NULL; + index = UT_LIST_GET_NEXT(indexes, index)) { + + if (dict_index_is_auto_gen_clust(index)) { + + /* GEN_CLUST_INDEX should use merge_threshold_table */ + + /* x-lock index is needed to exclude concurrent + pessimistic tree operations */ + index->lock.x_lock(SRW_LOCK_CALL); + index->merge_threshold = merge_threshold_table + & ((1U << 6) - 1); + index->lock.x_unlock(); + + continue; + } + + for (uint i = 0; i < table_share->keys; i++) { + if (is_found[i]) { + continue; + } + + KEY* key_info = &table_share->key_info[i]; + + if (innobase_strcasecmp( + index->name, key_info->name.str) == 0) { + + /* x-lock index is needed to exclude concurrent + pessimistic tree operations */ + index->lock.x_lock(SRW_LOCK_CALL); + index->merge_threshold + = merge_threshold_index[i] + & ((1U << 6) - 1); + index->lock.x_unlock(); + is_found[i] = true; + + break; + } + } + } +} + +/** Set m_use_* flags. */ +void +create_table_info_t::set_tablespace_type( + bool table_being_altered_is_file_per_table) +{ + /** Allow file_per_table for this table either because: + 1) the setting innodb_file_per_table=on, + 2) the table being altered is currently file_per_table */ + m_allow_file_per_table = + m_innodb_file_per_table + || table_being_altered_is_file_per_table; + + /* Ignore the current innodb-file-per-table setting if we are + creating a temporary table. */ + m_use_file_per_table = m_allow_file_per_table + && !m_create_info->tmp_table(); + + /* DATA DIRECTORY must have m_use_file_per_table but cannot be + used with TEMPORARY tables. */ + m_use_data_dir = + m_use_file_per_table + && m_create_info->data_file_name + && m_create_info->data_file_name[0] + && my_use_symdir; +} + +/** Initialize the create_table_info_t object. +@return error number */ +int +create_table_info_t::initialize() +{ + DBUG_ENTER("create_table_info_t::initialize"); + + ut_ad(m_thd != NULL); + ut_ad(m_create_info != NULL); + + if (m_form->s->fields > REC_MAX_N_USER_FIELDS) { + DBUG_RETURN(HA_ERR_TOO_MANY_FIELDS); + } + + /* Check for name conflicts (with reserved name) for + any user indices to be created. */ + if (innobase_index_name_is_reserved(m_thd, m_form->key_info, + m_form->s->keys)) { + DBUG_RETURN(HA_ERR_WRONG_INDEX); + } + + /* Get the transaction associated with the current thd, or create one + if not yet created */ + + check_trx_exists(m_thd); + + DBUG_RETURN(0); +} + + +/** Check if a virtual column is part of a fulltext or spatial index. */ +bool +create_table_info_t::gcols_in_fulltext_or_spatial() +{ + for (ulint i = 0; i < m_form->s->keys; i++) { + const KEY* key = m_form->key_info + i; + if (!(key->flags & (HA_SPATIAL | HA_FULLTEXT))) { + continue; + } + for (ulint j = 0; j < key->user_defined_key_parts; j++) { + /* We do not support special (Fulltext or + Spatial) index on virtual columns */ + if (!key->key_part[j].field->stored_in_db()) { + my_error(ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN, MYF(0)); + return true; + } + } + } + return false; +} + + +/** Prepare to create a new table to an InnoDB database. +@param[in] name Table name +@return error number */ +int create_table_info_t::prepare_create_table(const char* name, bool strict) +{ + DBUG_ENTER("prepare_create_table"); + + ut_ad(m_thd != NULL); + ut_ad(m_create_info != NULL); + + set_tablespace_type(false); + + normalize_table_name(m_table_name, name); + + /* Validate table options not handled by the SQL-parser */ + if (check_table_options()) { + DBUG_RETURN(HA_WRONG_CREATE_OPTION); + } + + /* Validate the create options if innodb_strict_mode is set. + Do not use the regular message for ER_ILLEGAL_HA_CREATE_OPTION + because InnoDB might actually support the option, but not under + the current conditions. The messages revealing the specific + problems are reported inside this function. */ + if (strict && create_options_are_invalid()) { + DBUG_RETURN(HA_WRONG_CREATE_OPTION); + } + + /* Create the table flags and flags2 */ + if (!innobase_table_flags()) { + DBUG_RETURN(HA_WRONG_CREATE_OPTION); + } + + if (high_level_read_only) { + DBUG_RETURN(HA_ERR_TABLE_READONLY); + } + + if (gcols_in_fulltext_or_spatial()) { + DBUG_RETURN(HA_ERR_UNSUPPORTED); + } + + for (uint i = 0; i < m_form->s->keys; i++) { + const size_t max_field_len + = DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(m_flags); + const KEY& key = m_form->key_info[i]; + + if (key.algorithm == HA_KEY_ALG_FULLTEXT) { + continue; + } + + if (too_big_key_part_length(max_field_len, key)) { + DBUG_RETURN(convert_error_code_to_mysql( + DB_TOO_BIG_INDEX_COL, m_flags, NULL)); + } + } + + DBUG_RETURN(parse_table_name(name)); +} + +/** Push warning message to SQL-layer based on foreign key constraint index +match error. +@param[in] trx Current transaction +@param[in] operation Operation ("Create" or "Alter") +@param[in] create_name Table name as specified in SQL +@param[in] columns Foreign key column names array +@param[in] index_error Index error code +@param[in] err_col Column where error happened +@param[in] err_index Index where error happened +@param[in] table Table object */ +static void +foreign_push_index_error(trx_t* trx, const char* operation, + const char* create_name, const char* fk_text, + const char** columns, fkerr_t index_error, + ulint err_col, dict_index_t* err_index, + dict_table_t* table) +{ + switch (index_error) { + case FK_SUCCESS: + break; + case FK_INDEX_NOT_FOUND: + ib_foreign_warn(trx, DB_CANNOT_ADD_CONSTRAINT, create_name, + "%s table %s with foreign key %s constraint" + " failed. There is no index in the referenced" + " table where the referenced columns appear" + " as the first columns.", + operation, create_name, fk_text); + return; + case FK_IS_PREFIX_INDEX: + ib_foreign_warn( + trx, DB_CANNOT_ADD_CONSTRAINT, create_name, + "%s table %s with foreign key %s constraint" + " failed. There is only prefix index in the referenced" + " table where the referenced columns appear" + " as the first columns.", + operation, create_name, fk_text); + return; + case FK_COL_NOT_NULL: + ib_foreign_warn( + trx, DB_CANNOT_ADD_CONSTRAINT, create_name, + "%s table %s with foreign key %s constraint" + " failed. You have defined a SET NULL condition but " + "column '%s' on index is defined as NOT NULL.", + operation, create_name, fk_text, columns[err_col]); + return; + case FK_COLS_NOT_EQUAL: + dict_field_t* field; + const char* col_name; + field = dict_index_get_nth_field(err_index, err_col); + + col_name = field->col->is_virtual() + ? "(null)" + : dict_table_get_col_name( + table, dict_col_get_no(field->col)); + ib_foreign_warn( + trx, DB_CANNOT_ADD_CONSTRAINT, create_name, + "%s table %s with foreign key %s constraint" + " failed. Field type or character set for column '%s' " + "does not match referenced column '%s'.", + operation, create_name, fk_text, columns[err_col], + col_name); + return; + } + DBUG_ASSERT("unknown error" == 0); +} + +/** Find column or virtual column in table by its name. +@param[in] table Table where column is searched +@param[in] name Name to search for +@retval true if found +@retval false if not found */ +static bool +find_col(dict_table_t* table, const char** name) +{ + ulint i; + for (i = 0; i < dict_table_get_n_cols(table); i++) { + + const char* col_name = dict_table_get_col_name(table, i); + + if (0 == innobase_strcasecmp(col_name, *name)) { + /* Found */ + strcpy((char*)*name, col_name); + return true; + } + } + + for (i = 0; i < dict_table_get_n_v_cols(table); i++) { + + const char* col_name = dict_table_get_v_col_name(table, i); + + if (0 == innobase_strcasecmp(col_name, *name)) { + /* Found */ + strcpy((char*)*name, col_name); + return true; + } + } + return false; +} + +/** Foreign key printer for error messages. Prints FK name if it exists or +key part list in the form (col1, col2, col3, ...) */ +class key_text +{ + static const size_t MAX_TEXT = 48; + char buf[MAX_TEXT + 1]; + +public: + key_text(Key* key) + { + char* ptr = buf; + if (key->name.str) { + size_t len = std::min(key->name.length, MAX_TEXT - 2); + *(ptr++) = '`'; + memcpy(ptr, key->name.str, len); + ptr += len; + *(ptr++) = '`'; + *ptr = '\0'; + return; + } + *(ptr++) = '('; + List_iterator_fast<Key_part_spec> it(key->columns); + while (Key_part_spec* k = it++) { + /* 3 is etc continuation ("..."); + 2 is comma separator (", ") in case of next exists; + 1 is terminating ')' */ + if (MAX_TEXT - (size_t)(ptr - buf) + >= (it.peek() ? 3 + 2 + 1 : 3 + 1) + + k->field_name.length) { + memcpy(ptr, k->field_name.str, + k->field_name.length); + ptr += k->field_name.length; + if (it.peek()) { + *(ptr++) = ','; + *(ptr++) = ' '; + } + } else { + ut_ad((size_t)(ptr - buf) <= MAX_TEXT - 4); + memcpy(ptr, "...", 3); + ptr += 3; + break; + } + } + *(ptr++) = ')'; + *ptr = '\0'; + } + const char* str() { return buf; } +}; + +/** Create InnoDB foreign keys from MySQL alter_info. Collect all +dict_foreign_t items into local_fk_set and then add into system table. +@return DB_SUCCESS or specific error code */ +dberr_t +create_table_info_t::create_foreign_keys() +{ + dict_foreign_set local_fk_set; + dict_foreign_set_free local_fk_set_free(local_fk_set); + dberr_t error; + ulint number = 1; + static const unsigned MAX_COLS_PER_FK = 500; + const char* column_names[MAX_COLS_PER_FK]; + const char* ref_column_names[MAX_COLS_PER_FK]; + char create_name[MAX_DATABASE_NAME_LEN + 1 + + MAX_TABLE_NAME_LEN + 1]; + dict_index_t* index = NULL; + fkerr_t index_error = FK_SUCCESS; + dict_index_t* err_index = NULL; + ulint err_col; + const bool tmp_table = m_flags2 & DICT_TF2_TEMPORARY; + const CHARSET_INFO* cs = thd_charset(m_thd); + const char* operation = "Create "; + const char* name = m_table_name; + + enum_sql_command sqlcom = enum_sql_command(thd_sql_command(m_thd)); + + if (sqlcom == SQLCOM_ALTER_TABLE) { + dict_table_t* table_to_alter; + mem_heap_t* heap = mem_heap_create(10000); + ulint highest_id_so_far; + char* n = dict_get_referenced_table( + name, LEX_STRING_WITH_LEN(m_form->s->db), + LEX_STRING_WITH_LEN(m_form->s->table_name), + &table_to_alter, heap, cs); + + /* Starting from 4.0.18 and 4.1.2, we generate foreign key id's + in the format databasename/tablename_ibfk_[number], where + [number] is local to the table; look for the highest [number] + for table_to_alter, so that we can assign to new constraints + higher numbers. */ + + /* If we are altering a temporary table, the table name after + ALTER TABLE does not correspond to the internal table name, and + table_to_alter is NULL. TODO: should we fix this somehow? */ + + if (table_to_alter) { + n = table_to_alter->name.m_name; + highest_id_so_far = dict_table_get_highest_foreign_id( + table_to_alter); + } else { + highest_id_so_far = 0; + } + + char* bufend = innobase_convert_name( + create_name, sizeof create_name, n, strlen(n), m_thd); + create_name[bufend - create_name] = '\0'; + number = highest_id_so_far + 1; + mem_heap_free(heap); + operation = "Alter "; + } else if (strstr(name, "#P#") || strstr(name, "#p#")) { + /* Partitioned table */ + create_name[0] = '\0'; + } else { + char* bufend = innobase_convert_name(create_name, + sizeof create_name, + name, + strlen(name), m_thd); + create_name[bufend - create_name] = '\0'; + } + + Alter_info* alter_info = m_create_info->alter_info; + ut_ad(alter_info); + List_iterator_fast<Key> key_it(alter_info->key_list); + + dict_table_t* table = dict_sys.find_table({name,strlen(name)}); + if (!table) { + ib_foreign_warn(m_trx, DB_CANNOT_ADD_CONSTRAINT, create_name, + "%s table %s foreign key constraint" + " failed. Table not found.", + operation, create_name); + + return (DB_CANNOT_ADD_CONSTRAINT); + } + + while (Key* key = key_it++) { + if (key->type != Key::FOREIGN_KEY || key->old) + continue; + + if (tmp_table) { + ib_foreign_warn(m_trx, DB_CANNOT_ADD_CONSTRAINT, + create_name, + "%s table `%s`.`%s` with foreign key " + "constraint failed. " + "Temporary tables can't have " + "foreign key constraints.", + operation, m_form->s->db.str, + m_form->s->table_name.str); + + return (DB_CANNOT_ADD_CONSTRAINT); + } else if (!*create_name) { + ut_ad("should be unreachable" == 0); + return DB_CANNOT_ADD_CONSTRAINT; + } + + Foreign_key* fk = static_cast<Foreign_key*>(key); + Key_part_spec* col; + bool success; + + dict_foreign_t* foreign = dict_mem_foreign_create(); + if (!foreign) { + return (DB_OUT_OF_MEMORY); + } + + List_iterator_fast<Key_part_spec> col_it(fk->columns); + unsigned i = 0, j = 0; + while ((col = col_it++)) { + column_names[i] = mem_heap_strdupl( + foreign->heap, col->field_name.str, + col->field_name.length); + success = find_col(table, column_names + i); + if (!success) { + key_text k(fk); + ib_foreign_warn( + m_trx, DB_CANNOT_ADD_CONSTRAINT, + create_name, + "%s table %s foreign key %s constraint" + " failed. Column %s was not found.", + operation, create_name, k.str(), + column_names[i]); + dict_foreign_free(foreign); + return (DB_CANNOT_ADD_CONSTRAINT); + } + ++i; + if (i >= MAX_COLS_PER_FK) { + key_text k(fk); + ib_foreign_warn( + m_trx, DB_CANNOT_ADD_CONSTRAINT, + create_name, + "%s table %s foreign key %s constraint" + " failed. Too many columns: %u (%u " + "allowed).", + operation, create_name, k.str(), i, + MAX_COLS_PER_FK); + dict_foreign_free(foreign); + return (DB_CANNOT_ADD_CONSTRAINT); + } + } + + index = dict_foreign_find_index( + table, NULL, column_names, i, NULL, TRUE, FALSE, + &index_error, &err_col, &err_index); + + if (!index) { + key_text k(fk); + foreign_push_index_error(m_trx, operation, create_name, + k.str(), column_names, + index_error, err_col, + err_index, table); + dict_foreign_free(foreign); + return (DB_CANNOT_ADD_CONSTRAINT); + } + + if (fk->constraint_name.str) { + ulint db_len; + + /* Catenate 'databasename/' to the constraint name + specified by the user: we conceive the constraint as + belonging to the same MySQL 'database' as the table + itself. We store the name to foreign->id. */ + + db_len = dict_get_db_name_len(table->name.m_name); + + foreign->id = static_cast<char*>(mem_heap_alloc( + foreign->heap, + db_len + fk->constraint_name.length + 2)); + + memcpy(foreign->id, table->name.m_name, db_len); + foreign->id[db_len] = '/'; + strcpy(foreign->id + db_len + 1, + fk->constraint_name.str); + } + + if (foreign->id == NULL) { + error = dict_create_add_foreign_id( + &number, table->name.m_name, foreign); + if (error != DB_SUCCESS) { + dict_foreign_free(foreign); + return (error); + } + } + + std::pair<dict_foreign_set::iterator, bool> ret + = local_fk_set.insert(foreign); + + if (!ret.second) { + /* A duplicate foreign key name has been found */ + dict_foreign_free(foreign); + return (DB_CANNOT_ADD_CONSTRAINT); + } + + foreign->foreign_table = table; + foreign->foreign_table_name + = mem_heap_strdup(foreign->heap, table->name.m_name); + if (!foreign->foreign_table_name) { + return (DB_OUT_OF_MEMORY); + } + + dict_mem_foreign_table_name_lookup_set(foreign, TRUE); + + foreign->foreign_index = index; + foreign->n_fields = i & dict_index_t::MAX_N_FIELDS; + + foreign->foreign_col_names = static_cast<const char**>( + mem_heap_alloc(foreign->heap, i * sizeof(void*))); + if (!foreign->foreign_col_names) { + return (DB_OUT_OF_MEMORY); + } + + memcpy(foreign->foreign_col_names, column_names, + i * sizeof(void*)); + + foreign->referenced_table_name = dict_get_referenced_table( + name, LEX_STRING_WITH_LEN(fk->ref_db), + LEX_STRING_WITH_LEN(fk->ref_table), + &foreign->referenced_table, foreign->heap, cs); + + if (!foreign->referenced_table_name) { + return (DB_OUT_OF_MEMORY); + } + + if (!foreign->referenced_table && m_trx->check_foreigns) { + char buf[MAX_TABLE_NAME_LEN + 1] = ""; + char* bufend; + + bufend = innobase_convert_name( + buf, MAX_TABLE_NAME_LEN, + foreign->referenced_table_name, + strlen(foreign->referenced_table_name), m_thd); + buf[bufend - buf] = '\0'; + key_text k(fk); + ib_foreign_warn(m_trx, DB_CANNOT_ADD_CONSTRAINT, + create_name, + "%s table %s with foreign key %s " + "constraint failed. Referenced table " + "%s not found in the data dictionary.", + operation, create_name, k.str(), buf); + return (DB_CANNOT_ADD_CONSTRAINT); + } + + /* Don't allow foreign keys on partitioned tables yet. */ + if (foreign->referenced_table + && dict_table_is_partition(foreign->referenced_table)) { + /* How could one make a referenced table to be a + * partition? */ + ut_ad(0); + my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, + MYF(0), "FOREIGN KEY"); + return (DB_CANNOT_ADD_CONSTRAINT); + } + + col_it.init(fk->ref_columns); + while ((col = col_it++)) { + ref_column_names[j] = mem_heap_strdupl( + foreign->heap, col->field_name.str, + col->field_name.length); + if (foreign->referenced_table) { + success = find_col(foreign->referenced_table, + ref_column_names + j); + if (!success) { + key_text k(fk); + ib_foreign_warn( + m_trx, + DB_CANNOT_ADD_CONSTRAINT, + create_name, + "%s table %s foreign key %s " + "constraint failed. " + "Column %s was not found.", + operation, create_name, + k.str(), ref_column_names[j]); + + return (DB_CANNOT_ADD_CONSTRAINT); + } + } + ++j; + } + /* See ER_WRONG_FK_DEF in mysql_prepare_create_table() */ + ut_ad(i == j); + + /* Try to find an index which contains the columns as the first + fields and in the right order, and the types are the same as in + foreign->foreign_index */ + + if (foreign->referenced_table) { + index = dict_foreign_find_index( + foreign->referenced_table, NULL, + ref_column_names, i, foreign->foreign_index, + TRUE, FALSE, &index_error, &err_col, + &err_index); + + if (!index) { + key_text k(fk); + foreign_push_index_error( + m_trx, operation, create_name, k.str(), + column_names, index_error, err_col, + err_index, foreign->referenced_table); + + return (DB_CANNOT_ADD_CONSTRAINT); + } + } else { + ut_a(m_trx->check_foreigns == FALSE); + index = NULL; + } + + foreign->referenced_index = index; + dict_mem_referenced_table_name_lookup_set(foreign, TRUE); + + foreign->referenced_col_names = static_cast<const char**>( + mem_heap_alloc(foreign->heap, i * sizeof(void*))); + if (!foreign->referenced_col_names) { + return (DB_OUT_OF_MEMORY); + } + + memcpy(foreign->referenced_col_names, ref_column_names, + i * sizeof(void*)); + + if (fk->delete_opt == FK_OPTION_SET_NULL + || fk->update_opt == FK_OPTION_SET_NULL) { + for (j = 0; j < foreign->n_fields; j++) { + if ((dict_index_get_nth_col( + foreign->foreign_index, j) + ->prtype) + & DATA_NOT_NULL) { + const dict_col_t* col + = dict_index_get_nth_col( + foreign->foreign_index, + j); + const char* col_name + = dict_table_get_col_name( + foreign->foreign_index + ->table, + dict_col_get_no(col)); + + /* It is not sensible to define SET + NULL + if the column is not allowed to be + NULL! */ + key_text k(fk); + ib_foreign_warn( + m_trx, + DB_CANNOT_ADD_CONSTRAINT, + create_name, + "%s table %s with foreign key " + "%s constraint failed. You have" + " defined a SET NULL condition " + "but column '%s' is defined as " + "NOT NULL.", + operation, create_name, + k.str(), col_name); + + return (DB_CANNOT_ADD_CONSTRAINT); + } + } + } + + switch (fk->delete_opt) { + case FK_OPTION_UNDEF: + case FK_OPTION_RESTRICT: + break; + case FK_OPTION_CASCADE: + foreign->type |= DICT_FOREIGN_ON_DELETE_CASCADE; + break; + case FK_OPTION_SET_NULL: + foreign->type |= DICT_FOREIGN_ON_DELETE_SET_NULL; + break; + case FK_OPTION_NO_ACTION: + foreign->type |= DICT_FOREIGN_ON_DELETE_NO_ACTION; + break; + case FK_OPTION_SET_DEFAULT: + // TODO: MDEV-10393 Foreign keys SET DEFAULT action + break; + default: + ut_ad(0); + break; + } + + switch (fk->update_opt) { + case FK_OPTION_UNDEF: + case FK_OPTION_RESTRICT: + break; + case FK_OPTION_CASCADE: + foreign->type |= DICT_FOREIGN_ON_UPDATE_CASCADE; + break; + case FK_OPTION_SET_NULL: + foreign->type |= DICT_FOREIGN_ON_UPDATE_SET_NULL; + break; + case FK_OPTION_NO_ACTION: + foreign->type |= DICT_FOREIGN_ON_UPDATE_NO_ACTION; + break; + case FK_OPTION_SET_DEFAULT: + // TODO: MDEV-10393 Foreign keys SET DEFAULT action + break; + default: + ut_ad(0); + break; + } + } + + if (dict_foreigns_has_s_base_col(local_fk_set, table)) { + return (DB_NO_FK_ON_S_BASE_COL); + } + + /**********************************************************/ + /* The following call adds the foreign key constraints + to the data dictionary system tables on disk */ + m_trx->op_info = "adding foreign keys"; + + trx_start_if_not_started_xa(m_trx, true); + + m_trx->dict_operation = true; + + error = dict_create_add_foreigns_to_dictionary(local_fk_set, table, + m_trx); + + if (error == DB_SUCCESS) { + + table->foreign_set.insert(local_fk_set.begin(), + local_fk_set.end()); + std::for_each(local_fk_set.begin(), local_fk_set.end(), + dict_foreign_add_to_referenced_table()); + local_fk_set.clear(); + + dict_mem_table_fill_foreign_vcol_set(table); + } + return (error); +} + +/** Create the internal innodb table. +@param create_fk whether to add FOREIGN KEY constraints */ +int create_table_info_t::create_table(bool create_fk) +{ + int error; + int primary_key_no; + uint i; + + DBUG_ENTER("create_table"); + + /* Look for a primary key */ + primary_key_no = (m_form->s->primary_key != MAX_KEY ? + (int) m_form->s->primary_key : -1); + + /* Our function innobase_get_mysql_key_number_for_index assumes + the primary key is always number 0, if it exists */ + ut_a(primary_key_no == -1 || primary_key_no == 0); + + error = create_table_def(); + + if (error) { + DBUG_RETURN(error); + } + + /* Create the keys */ + + if (m_form->s->keys == 0 || primary_key_no == -1) { + /* Create an index which is used as the clustered index; + order the rows by their row id which is internally generated + by InnoDB */ + ulint flags = m_table->flags; + dict_index_t* index = dict_mem_index_create( + m_table, innobase_index_reserve_name, + DICT_CLUSTERED, 0); + const ha_table_option_struct& o = *m_form->s->option_struct; + error = convert_error_code_to_mysql( + row_create_index_for_mysql( + index, m_trx, NULL, + fil_encryption_t(o.encryption), + uint32_t(o.encryption_key_id)), + flags, m_thd); + if (error) { + DBUG_RETURN(error); + } + } + + if (primary_key_no != -1) { + /* In InnoDB the clustered index must always be created + first */ + if ((error = create_index(m_trx, m_form, m_table, + (uint) primary_key_no))) { + DBUG_RETURN(error); + } + } + + /* Create the ancillary tables that are common to all FTS indexes on + this table. */ + if (m_flags2 & DICT_TF2_FTS) { + fts_doc_id_index_enum ret; + + /* Check whether there already exists FTS_DOC_ID_INDEX */ + ret = innobase_fts_check_doc_id_index_in_def( + m_form->s->keys, m_form->key_info); + + switch (ret) { + case FTS_INCORRECT_DOC_ID_INDEX: + push_warning_printf(m_thd, + Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_NAME_FOR_INDEX, + " InnoDB: Index name %s is reserved" + " for the unique index on" + " FTS_DOC_ID column for FTS" + " Document ID indexing" + " on table %s. Please check" + " the index definition to" + " make sure it is of correct" + " type\n", + FTS_DOC_ID_INDEX_NAME, + m_table->name.m_name); + + if (m_table->fts) { + m_table->fts->~fts_t(); + m_table->fts = nullptr; + } + + my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), + FTS_DOC_ID_INDEX_NAME); + DBUG_RETURN(-1); + case FTS_EXIST_DOC_ID_INDEX: + case FTS_NOT_EXIST_DOC_ID_INDEX: + break; + } + + dberr_t err = fts_create_common_tables( + m_trx, m_table, + (ret == FTS_EXIST_DOC_ID_INDEX)); + + error = convert_error_code_to_mysql(err, 0, NULL); + + if (error) { + DBUG_RETURN(error); + } + } + + for (i = 0; i < m_form->s->keys; i++) { + if (i != uint(primary_key_no) + && (error = create_index(m_trx, m_form, m_table, i))) { + DBUG_RETURN(error); + } + } + + /* Cache all the FTS indexes on this table in the FTS specific + structure. They are used for FTS indexed column update handling. */ + if (m_flags2 & DICT_TF2_FTS) { + fts_t* fts = m_table->fts; + + ut_a(fts != NULL); + + dict_table_get_all_fts_indexes(m_table, fts->indexes); + } + + dberr_t err = create_fk ? create_foreign_keys() : DB_SUCCESS; + + if (err == DB_SUCCESS) { + const dict_err_ignore_t ignore_err = m_trx->check_foreigns + ? DICT_ERR_IGNORE_NONE : DICT_ERR_IGNORE_FK_NOKEY; + + /* Check that also referencing constraints are ok */ + dict_names_t fk_tables; + err = dict_load_foreigns(m_table_name, nullptr, + m_trx->id, true, + ignore_err, fk_tables); + while (err == DB_SUCCESS && !fk_tables.empty()) { + dict_sys.load_table( + {fk_tables.front(), strlen(fk_tables.front())}, + ignore_err); + fk_tables.pop_front(); + } + } + + switch (err) { + case DB_PARENT_NO_INDEX: + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_ERR_CANNOT_ADD_FOREIGN, + "Create table '%s' with foreign key constraint" + " failed. There is no index in the referenced" + " table where the referenced columns appear" + " as the first columns.\n", m_table_name); + break; + + case DB_CHILD_NO_INDEX: + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_ERR_CANNOT_ADD_FOREIGN, + "Create table '%s' with foreign key constraint" + " failed. There is no index in the referencing" + " table where referencing columns appear" + " as the first columns.\n", m_table_name); + break; + case DB_NO_FK_ON_S_BASE_COL: + push_warning_printf( + m_thd, Sql_condition::WARN_LEVEL_WARN, + HA_ERR_CANNOT_ADD_FOREIGN, + "Create table '%s' with foreign key constraint" + " failed. Cannot add foreign key constraint" + " placed on the base column of stored" + " column. \n", + m_table_name); + default: + break; + } + + if (err != DB_SUCCESS) { + DBUG_RETURN(convert_error_code_to_mysql( + err, m_flags, NULL)); + } + + /* In TRUNCATE TABLE, we will merely warn about the maximum + row size being too large. */ + if (!row_size_is_acceptable(*m_table, create_fk)) { + DBUG_RETURN(convert_error_code_to_mysql( + DB_TOO_BIG_RECORD, m_flags, NULL)); + } + + DBUG_RETURN(0); +} + +bool create_table_info_t::row_size_is_acceptable( + const dict_table_t &table, bool strict) const +{ + for (dict_index_t *index= dict_table_get_first_index(&table); index; + index= dict_table_get_next_index(index)) + if (!row_size_is_acceptable(*index, strict)) + return false; + return true; +} + +dict_index_t::record_size_info_t dict_index_t::record_size_info() const +{ + ut_ad(!(type & DICT_FTS)); + + /* maximum allowed size of a node pointer record */ + ulint page_ptr_max; + const bool comp= table->not_redundant(); + /* table->space == NULL after DISCARD TABLESPACE */ + const ulint zip_size= dict_tf_get_zip_size(table->flags); + record_size_info_t result; + + if (zip_size && zip_size < srv_page_size) + { + /* On a ROW_FORMAT=COMPRESSED page, two records must fit in the + uncompressed page modification log. On compressed pages + with size.physical() == univ_page_size.physical(), + this limit will never be reached. */ + ut_ad(comp); + /* The maximum allowed record size is the size of + an empty page, minus a byte for recoding the heap + number in the page modification log. The maximum + allowed node pointer size is half that. */ + result.max_leaf_size= page_zip_empty_size(n_fields, zip_size); + if (result.max_leaf_size) + { + result.max_leaf_size--; + } + page_ptr_max= result.max_leaf_size / 2; + /* On a compressed page, there is a two-byte entry in + the dense page directory for every record. But there + is no record header. */ + result.shortest_size= 2; + } + else + { + /* The maximum allowed record size is half a B-tree + page(16k for 64k page size). No additional sparse + page directory entry will be generated for the first + few user records. */ + result.max_leaf_size= (comp || srv_page_size < UNIV_PAGE_SIZE_MAX) + ? page_get_free_space_of_empty(comp) / 2 + : REDUNDANT_REC_MAX_DATA_SIZE; + + page_ptr_max= result.max_leaf_size; + /* Each record has a header. */ + result.shortest_size= comp ? REC_N_NEW_EXTRA_BYTES : REC_N_OLD_EXTRA_BYTES; + } + + if (comp) + { + /* Include the "null" flags in the + maximum possible record size. */ + result.shortest_size+= UT_BITS_IN_BYTES(n_nullable); + } + else + { + /* For each column, include a 2-byte offset and a + "null" flag. The 1-byte format is only used in short + records that do not contain externally stored columns. + Such records could never exceed the page limit, even + when using the 2-byte format. */ + result.shortest_size+= 2 * n_fields; + } + + const ulint max_local_len= table->get_overflow_field_local_len(); + + /* Compute the maximum possible record size. */ + for (unsigned i= 0; i < n_fields; i++) + { + const dict_field_t &f= fields[i]; + const dict_col_t &col= *f.col; + + /* In dtuple_convert_big_rec(), variable-length columns + that are longer than BTR_EXTERN_LOCAL_STORED_MAX_SIZE + may be chosen for external storage. + + Fixed-length columns, and all columns of secondary + index records are always stored inline. */ + + /* Determine the maximum length of the index field. + The field_ext_max_size should be computed as the worst + case in rec_get_converted_size_comp() for + REC_STATUS_ORDINARY records. */ + + size_t field_max_size= dict_col_get_fixed_size(&col, comp); + if (field_max_size && f.fixed_len != 0) + { + /* dict_index_add_col() should guarantee this */ + ut_ad(!f.prefix_len || f.fixed_len == f.prefix_len); + if (f.prefix_len) + field_max_size= f.prefix_len; + /* Fixed lengths are not encoded + in ROW_FORMAT=COMPACT. */ + goto add_field_size; + } + + field_max_size= dict_col_get_max_size(&col); + + if (f.prefix_len) + { + if (f.prefix_len < field_max_size) + { + field_max_size= f.prefix_len; + } + + /* those conditions were copied from dtuple_convert_big_rec()*/ + } + else if (field_max_size > max_local_len && + field_max_size > BTR_EXTERN_LOCAL_STORED_MAX_SIZE && + DATA_BIG_COL(&col) && dict_index_is_clust(this)) + { + + /* In the worst case, we have a locally stored + column of BTR_EXTERN_LOCAL_STORED_MAX_SIZE bytes. + The length can be stored in one byte. If the + column were stored externally, the lengths in + the clustered index page would be + BTR_EXTERN_FIELD_REF_SIZE and 2. */ + field_max_size= max_local_len; + } + + if (comp) + { + /* Add the extra size for ROW_FORMAT=COMPACT. + For ROW_FORMAT=REDUNDANT, these bytes were + added to result.shortest_size before this loop. */ + result.shortest_size+= field_max_size < 256 ? 1 : 2; + } + add_field_size: + result.shortest_size+= field_max_size; + + /* Check the size limit on leaf pages. */ + if (result.shortest_size >= result.max_leaf_size) + { + result.set_too_big(i); + } + + /* Check the size limit on non-leaf pages. Records + stored in non-leaf B-tree pages consist of the unique + columns of the record (the key columns of the B-tree) + and a node pointer field. When we have processed the + unique columns, result.shortest_size equals the size of the + node pointer record minus the node pointer column. */ + if (i + 1 == dict_index_get_n_unique_in_tree(this) && + result.shortest_size + REC_NODE_PTR_SIZE + (comp ? 0 : 2) >= + page_ptr_max) + { + result.set_too_big(i); + } + } + + return result; +} + +/** Issue a warning that the row is too big. */ +static void ib_warn_row_too_big(THD *thd, const dict_table_t *table) +{ + /* FIXME: this row size check should be improved */ + /* If prefix is true then a 768-byte prefix is stored + locally for BLOB fields. Refer to dict_table_get_format() */ + const bool prefix= !dict_table_has_atomic_blobs(table); + + const ulint free_space= + page_get_free_space_of_empty(table->flags & DICT_TF_COMPACT) / 2; + + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, HA_ERR_TO_BIG_ROW, + "Row size too large (> " ULINTPF "). Changing some columns to TEXT" + " or BLOB %smay help. In current row format, BLOB prefix of" + " %d bytes is stored inline.", + free_space, + prefix ? "or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED " : "", + prefix ? DICT_MAX_FIXED_COL_LEN : 0); +} + +bool create_table_info_t::row_size_is_acceptable( + const dict_index_t &index, bool strict) const +{ + if ((index.type & DICT_FTS) || index.table->is_system_db) + { + /* Ignore system tables check because innodb_table_stats + maximum row size can not fit on 4k page. */ + return true; + } + + const bool innodb_strict_mode= THDVAR(m_thd, strict_mode); + dict_index_t::record_size_info_t info= index.record_size_info(); + + if (info.row_is_too_big()) + { + ut_ad(info.get_overrun_size() != 0); + + const size_t idx= info.get_first_overrun_field_index(); + const dict_field_t *field= dict_index_get_nth_field(&index, idx); + + ut_ad((!field->name) == field->col->is_dropped()); + if (innodb_strict_mode || global_system_variables.log_warnings > 2) + { + ib::error_or_warn eow(strict && innodb_strict_mode); + if (field->name) + eow << "Cannot add field " << field->name << " in table "; + else + eow << "Cannot add an instantly dropped column in table "; + eow << "`" << m_form->s->db.str << "`.`" << m_form->s->table_name.str + << "`" " because after adding it, the row size is " + << info.get_overrun_size() + << " which is greater than maximum allowed size (" + << info.max_leaf_size << " bytes) for a record on index leaf page."; + } + + if (strict && innodb_strict_mode) + return false; + + ib_warn_row_too_big(m_thd, index.table); + } + + return true; +} + +void create_table_info_t::create_table_update_dict(dict_table_t *table, + THD *thd, + const HA_CREATE_INFO &info, + const TABLE &t) +{ + ut_ad(dict_sys.locked()); + + DBUG_ASSERT(table->get_ref_count()); + if (table->fts) + { + if (!table->fts_doc_id_index) + table->fts_doc_id_index= + dict_table_get_index_on_name(table, FTS_DOC_ID_INDEX_NAME); + else + DBUG_ASSERT(table->fts_doc_id_index == + dict_table_get_index_on_name(table, FTS_DOC_ID_INDEX_NAME)); + } + + DBUG_ASSERT(!table->fts == !table->fts_doc_id_index); + + innobase_copy_frm_flags_from_create_info(table, &info); + + /* Load server stopword into FTS cache */ + if (table->flags2 & DICT_TF2_FTS && + innobase_fts_load_stopword(table, nullptr, thd)) + fts_optimize_add_table(table); + + if (const Field *ai = t.found_next_number_field) + { + ut_ad(ai->stored_in_db()); + ib_uint64_t autoinc= info.auto_increment_value; + if (autoinc == 0) + autoinc= 1; + + table->autoinc_mutex.wr_lock(); + dict_table_autoinc_initialize(table, autoinc); + + if (!table->is_temporary()) + { + const unsigned col_no= innodb_col_no(ai); + table->persistent_autoinc= static_cast<uint16_t> + (dict_table_get_nth_col_pos(table, col_no, nullptr) + 1) & + dict_index_t::MAX_N_FIELDS; + /* Persist the "last used" value, which typically is AUTO_INCREMENT - 1. + In btr_create(), the value 0 was already written. */ + if (--autoinc) + btr_write_autoinc(dict_table_get_first_index(table), autoinc); + } + + table->autoinc_mutex.wr_unlock(); + } + + innobase_parse_hint_from_comment(thd, table, t.s); +} + +/** Allocate a new trx. */ +void +create_table_info_t::allocate_trx() +{ + m_trx = innobase_trx_allocate(m_thd); + m_trx->will_lock = true; +} + +/** Create a new table to an InnoDB database. +@param[in] name Table name, format: "db/table_name". +@param[in] form Table format; columns and index information. +@param[in] create_info Create info (including create statement string). +@param[in] file_per_table whether to create .ibd file +@param[in,out] trx dictionary transaction, or NULL to create new +@return error code +@retval 0 on success */ +int +ha_innobase::create(const char *name, TABLE *form, HA_CREATE_INFO *create_info, + bool file_per_table, trx_t *trx= nullptr) +{ + char norm_name[FN_REFLEN]; /* {database}/{tablename} */ + char remote_path[FN_REFLEN]; /* Absolute path of table */ + + DBUG_ENTER("ha_innobase::create"); + DBUG_ASSERT(form->s == table_share); + DBUG_ASSERT(table_share->table_type == TABLE_TYPE_SEQUENCE || + table_share->table_type == TABLE_TYPE_NORMAL); + + create_table_info_t info(ha_thd(), form, create_info, norm_name, + remote_path, file_per_table, trx); + + int error= info.initialize(); + if (!error) + error= info.prepare_create_table(name, !trx); + if (error) + DBUG_RETURN(error); + + const bool own_trx= !trx; + if (own_trx) + { + info.allocate_trx(); + trx= info.trx(); + DBUG_ASSERT(trx_state_eq(trx, TRX_STATE_NOT_STARTED)); + + if (!(info.flags2() & DICT_TF2_TEMPORARY)) + { + trx_start_for_ddl(trx); + if (dberr_t err= lock_sys_tables(trx)) + error= convert_error_code_to_mysql(err, 0, nullptr); + } + row_mysql_lock_data_dictionary(trx); + } + + if (!error) + error= info.create_table(own_trx); + + if (own_trx || (info.flags2() & DICT_TF2_TEMPORARY)) + { + if (error) + trx_rollback_for_mysql(trx); + else + { + std::vector<pfs_os_file_t> deleted; + trx->commit(deleted); + ut_ad(deleted.empty()); + info.table()->acquire(); + info.create_table_update_dict(info.table(), info.thd(), + *create_info, *form); + } + + if (own_trx) + { + row_mysql_unlock_data_dictionary(trx); + + if (!error) + { + dict_stats_update(info.table(), DICT_STATS_EMPTY_TABLE); + if (!info.table()->is_temporary()) + log_write_up_to(trx->commit_lsn, true); + info.table()->release(); + } + trx->free(); + } + } + else if (!error && m_prebuilt) + m_prebuilt->table= info.table(); + + DBUG_RETURN(error); +} + +/** Create a new table to an InnoDB database. +@param[in] name Table name, format: "db/table_name". +@param[in] form Table format; columns and index information. +@param[in] create_info Create info (including create statement string). +@return 0 if success else error number. */ +int ha_innobase::create(const char *name, TABLE *form, + HA_CREATE_INFO *create_info) +{ + return create(name, form, create_info, srv_file_per_table); +} + +/*****************************************************************//** +Discards or imports an InnoDB tablespace. +@return 0 == success, -1 == error */ + +int +ha_innobase::discard_or_import_tablespace( +/*======================================*/ + my_bool discard) /*!< in: TRUE if discard, else import */ +{ + + DBUG_ENTER("ha_innobase::discard_or_import_tablespace"); + + ut_a(m_prebuilt->trx != NULL); + ut_a(m_prebuilt->trx->magic_n == TRX_MAGIC_N); + ut_a(m_prebuilt->trx == thd_to_trx(ha_thd())); + + if (is_read_only()) { + DBUG_RETURN(HA_ERR_TABLE_READONLY); + } + + if (m_prebuilt->table->is_temporary()) { + ib_senderrf( + m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_CANNOT_DISCARD_TEMPORARY_TABLE); + + DBUG_RETURN(HA_ERR_TABLE_NEEDS_UPGRADE); + } + + if (m_prebuilt->table->space == fil_system.sys_space) { + ib_senderrf( + m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLE_IN_SYSTEM_TABLESPACE, + m_prebuilt->table->name.m_name); + + DBUG_RETURN(HA_ERR_TABLE_NEEDS_UPGRADE); + } + + trx_start_if_not_started(m_prebuilt->trx, true); + m_prebuilt->trx->dict_operation = true; + + /* Obtain an exclusive lock on the table. */ + dberr_t err = lock_table_for_trx(m_prebuilt->table, + m_prebuilt->trx, LOCK_X); + if (err == DB_SUCCESS) { + err = lock_sys_tables(m_prebuilt->trx); + } + + if (err != DB_SUCCESS) { + /* unable to lock the table: do nothing */ + m_prebuilt->trx->commit(); + } else if (discard) { + + /* Discarding an already discarded tablespace should be an + idempotent operation. Also, if the .ibd file is missing the + user may want to set the DISCARD flag in order to IMPORT + a new tablespace. */ + + if (!m_prebuilt->table->is_readable()) { + ib_senderrf( + m_prebuilt->trx->mysql_thd, + IB_LOG_LEVEL_WARN, ER_TABLESPACE_MISSING, + m_prebuilt->table->name.m_name); + } + + err = row_discard_tablespace_for_mysql( + m_prebuilt->table, m_prebuilt->trx); + } else if (m_prebuilt->table->is_readable()) { + /* Commit the transaction in order to + release the table lock. */ + trx_commit_for_mysql(m_prebuilt->trx); + + ib::error() << "Unable to import tablespace " + << m_prebuilt->table->name << " because it already" + " exists. Please DISCARD the tablespace" + " before IMPORT."; + ib_senderrf( + m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_EXISTS, m_prebuilt->table->name.m_name); + + DBUG_RETURN(HA_ERR_TABLE_EXIST); + } else { + err = row_import_for_mysql(m_prebuilt->table, m_prebuilt); + + if (err == DB_SUCCESS) { + + info(HA_STATUS_TIME + | HA_STATUS_CONST + | HA_STATUS_VARIABLE + | HA_STATUS_AUTO); + + fil_crypt_set_encrypt_tables(srv_encrypt_tables); + } + } + + ut_ad(m_prebuilt->trx->state == TRX_STATE_NOT_STARTED); + + if (discard || err != DB_SUCCESS) { + DBUG_RETURN(convert_error_code_to_mysql( + err, m_prebuilt->table->flags, NULL)); + } + + if (dict_stats_is_persistent_enabled(m_prebuilt->table)) { + dberr_t ret; + + /* Adjust the persistent statistics. */ + ret = dict_stats_update(m_prebuilt->table, + DICT_STATS_RECALC_PERSISTENT); + + if (ret != DB_SUCCESS) { + push_warning_printf( + ha_thd(), + Sql_condition::WARN_LEVEL_WARN, + ER_ALTER_INFO, + "Error updating stats for table '%s'" + " after table rebuild: %s", + m_prebuilt->table->name.m_name, + ut_strerr(ret)); + } + } + + DBUG_RETURN(0); +} + + +/** DROP TABLE (possibly as part of DROP DATABASE, CREATE/ALTER TABLE) +@param name table name +@return error number */ +int ha_innobase::delete_table(const char *name) +{ + DBUG_ENTER("ha_innobase::delete_table"); + if (high_level_read_only) + DBUG_RETURN(HA_ERR_TABLE_READONLY); + + THD *thd= ha_thd(); + + DBUG_EXECUTE_IF("test_normalize_table_name_low", + test_normalize_table_name_low();); + DBUG_EXECUTE_IF("test_ut_format_name", test_ut_format_name();); + + trx_t *parent_trx= check_trx_exists(thd); + dict_table_t *table; + + { + char norm_name[FN_REFLEN]; + normalize_table_name(norm_name, name); + span<const char> n{norm_name, strlen(norm_name)}; + + dict_sys.lock(SRW_LOCK_CALL); + table= dict_sys.load_table(n, DICT_ERR_IGNORE_DROP); +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (!table && lower_case_table_names == 1 && is_partition(norm_name)) + { + IF_WIN(normalize_table_name_c_low(norm_name, name, false), + innobase_casedn_str(norm_name)); + table= dict_sys.load_table(n, DICT_ERR_IGNORE_DROP); + } +#endif + if (!table) + { + dict_sys.unlock(); + DBUG_RETURN(HA_ERR_NO_SUCH_TABLE); + } + } + + if (table->is_temporary()) + { + dict_sys.unlock(); + parent_trx->mod_tables.erase(table); /* CREATE...SELECT error handling */ + btr_drop_temporary_table(*table); + dict_sys.lock(SRW_LOCK_CALL); + dict_sys.remove(table); + dict_sys.unlock(); + DBUG_RETURN(0); + } + + table->acquire(); + dict_sys.unlock(); + + trx_t *trx= parent_trx; + dberr_t err= DB_SUCCESS; + if (!trx->lock.table_locks.empty() && + thd_ddl_options(trx->mysql_thd)->is_create_select()) + { + /* CREATE TABLE...PRIMARY KEY...SELECT ought to be dropping the + table because a duplicate key was detected or a timeout occurred. + + We shall hijack the existing transaction to drop the table and + commit the transaction. If this is a partitioned table, one + partition will use this hijacked transaction; others will use a + separate transaction, one per partition. */ + ut_ad(!trx->dict_operation_lock_mode); + ut_ad(trx->will_lock); + ut_ad(trx->state == TRX_STATE_ACTIVE); + trx->dict_operation= true; + } + else + { + trx= innobase_trx_allocate(thd); + trx_start_for_ddl(trx); + + if (table->name.is_temporary()) + /* There is no need to lock any FOREIGN KEY child tables. */; +#ifdef WITH_PARTITION_STORAGE_ENGINE + else if (table->name.part()) + /* FOREIGN KEY constraints cannot exist on partitioned tables. */; +#endif + else + { + dict_sys.freeze(SRW_LOCK_CALL); + for (const dict_foreign_t* f : table->referenced_set) + if (dict_table_t* child= f->foreign_table) + if ((err= lock_table_for_trx(child, trx, LOCK_X)) != DB_SUCCESS) + break; + dict_sys.unfreeze(); + } + } + + dict_table_t *table_stats= nullptr, *index_stats= nullptr; + MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr; + if (err == DB_SUCCESS) + err= lock_table_for_trx(table, trx, LOCK_X); + + const bool fts= err == DB_SUCCESS && + (table->flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)); + const enum_sql_command sqlcom= enum_sql_command(thd_sql_command(thd)); + + if (fts) + { + fts_optimize_remove_table(table); + purge_sys.stop_FTS(*table); + err= fts_lock_tables(trx, *table); + } + +#ifdef WITH_PARTITION_STORAGE_ENGINE + const bool rollback_add_partition= + (sqlcom == SQLCOM_ALTER_TABLE && table->name.part()); + + if (rollback_add_partition) + { + if (!fts) + purge_sys.stop_FTS(); + /* This looks like the rollback of ALTER TABLE...ADD PARTITION + that was caused by MDL timeout. We could have written undo log + for inserting the data into the new partitions. */ + if (table->stat_persistent != DICT_STATS_PERSISTENT_OFF) + { + /* We do not really know if we are holding MDL_EXCLUSIVE. Even + though this code is handling the case that we are not holding + it, we might actually hold it. We want to avoid a deadlock + with dict_stats_process_entry_from_recalc_pool(). */ + dict_stats_recalc_pool_del(table->id, true); + /* If statistics calculation is still using this table, we will + catch it below while waiting for purge to stop using this table. */ + } + } +#endif + + DEBUG_SYNC(thd, "before_delete_table_stats"); + + if (err == DB_SUCCESS && dict_stats_is_persistent_enabled(table) && + !table->is_stats_table()) + { + table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false, + DICT_ERR_IGNORE_NONE); + if (table_stats) + { + dict_sys.freeze(SRW_LOCK_CALL); + table_stats= dict_acquire_mdl_shared<false>(table_stats, + thd, &mdl_table); + dict_sys.unfreeze(); + } + + index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false, + DICT_ERR_IGNORE_NONE); + if (index_stats) + { + dict_sys.freeze(SRW_LOCK_CALL); + index_stats= dict_acquire_mdl_shared<false>(index_stats, + thd, &mdl_index); + dict_sys.unfreeze(); + } + + const bool skip_wait{table->name.is_temporary()}; + + if (table_stats && index_stats && + !strcmp(table_stats->name.m_name, TABLE_STATS_NAME) && + !strcmp(index_stats->name.m_name, INDEX_STATS_NAME) && + !(err= lock_table_for_trx(table_stats, trx, LOCK_X, skip_wait))) + err= lock_table_for_trx(index_stats, trx, LOCK_X, skip_wait); + + if (err != DB_SUCCESS && skip_wait) + { + /* We may skip deleting statistics if we cannot lock the tables, + when the table carries a temporary name. */ + ut_ad(err == DB_LOCK_WAIT); + ut_ad(trx->error_state == DB_SUCCESS); + err= DB_SUCCESS; + dict_table_close(table_stats, false, thd, mdl_table); + dict_table_close(index_stats, false, thd, mdl_index); + table_stats= nullptr; + index_stats= nullptr; + } + } + + if (err == DB_SUCCESS) + { + if (!table->space) + { + const char *data_dir_path= DICT_TF_HAS_DATA_DIR(table->flags) + ? table->data_dir_path : nullptr; + char *path= fil_make_filepath(data_dir_path, table->name, CFG, + data_dir_path != nullptr); + os_file_delete_if_exists(innodb_data_file_key, path, nullptr); + ut_free(path); + path= fil_make_filepath(data_dir_path, table->name, IBD, + data_dir_path != nullptr); + os_file_delete_if_exists(innodb_data_file_key, path, nullptr); + ut_free(path); + if (data_dir_path) + { + path= fil_make_filepath(nullptr, table->name, ISL, false); + os_file_delete_if_exists(innodb_data_file_key, path, nullptr); + ut_free(path); + } + } + err= lock_sys_tables(trx); + } + + dict_sys.lock(SRW_LOCK_CALL); + + if (!table->release() && err == DB_SUCCESS) + { + /* Wait for purge threads to stop using the table. */ + for (uint n= 15;;) + { + dict_sys.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + dict_sys.lock(SRW_LOCK_CALL); + + if (!--n) + { + err= DB_LOCK_WAIT_TIMEOUT; + break; + } + if (!table->get_ref_count()) + break; + } + } + + trx->dict_operation_lock_mode= true; + + if (err != DB_SUCCESS) + { +err_exit: + trx->dict_operation_lock_mode= false; + trx->rollback(); + switch (err) { + case DB_CANNOT_DROP_CONSTRAINT: + case DB_LOCK_WAIT_TIMEOUT: + break; + default: + ib::error() << "DROP TABLE " << table->name << ": " << err; + } + if (fts) + { + fts_optimize_add_table(table); + purge_sys.resume_FTS(); + } +#ifdef WITH_PARTITION_STORAGE_ENGINE + else if (rollback_add_partition) + purge_sys.resume_FTS(); +#endif + if (table_stats) + dict_table_close(table_stats, true, thd, mdl_table); + if (index_stats) + dict_table_close(index_stats, true, thd, mdl_index); + dict_sys.unlock(); + if (trx != parent_trx) + trx->free(); + DBUG_RETURN(convert_error_code_to_mysql(err, 0, NULL)); + } + + if (!table->no_rollback() && trx->check_foreigns) + { + const bool drop_db= sqlcom == SQLCOM_DROP_DB; + for (auto foreign : table->referenced_set) + { + /* We should allow dropping a referenced table if creating + that referenced table has failed for some reason. For example + if referenced table is created but it column types that are + referenced do not match. */ + if (foreign->foreign_table == table || + (drop_db && + dict_tables_have_same_db(table->name.m_name, + foreign->foreign_table_name_lookup))) + continue; + mysql_mutex_lock(&dict_foreign_err_mutex); + rewind(dict_foreign_err_file); + ut_print_timestamp(dict_foreign_err_file); + fputs(" Cannot drop table ", dict_foreign_err_file); + ut_print_name(dict_foreign_err_file, trx, table->name.m_name); + fputs("\nbecause it is referenced by ", dict_foreign_err_file); + ut_print_name(dict_foreign_err_file, trx, foreign->foreign_table_name); + putc('\n', dict_foreign_err_file); + mysql_mutex_unlock(&dict_foreign_err_mutex); + err= DB_CANNOT_DROP_CONSTRAINT; + goto err_exit; + } + } + + if (!table->no_rollback()) + err= trx->drop_table_foreign(table->name); + + if (err == DB_SUCCESS && table_stats && index_stats) + err= trx->drop_table_statistics(table->name); + if (err != DB_SUCCESS) + goto err_exit; + + err= trx->drop_table(*table); + if (err != DB_SUCCESS) + goto err_exit; + + std::vector<pfs_os_file_t> deleted; + trx->commit(deleted); + if (table_stats) + dict_table_close(table_stats, true, thd, mdl_table); + if (index_stats) + dict_table_close(index_stats, true, thd, mdl_index); + row_mysql_unlock_data_dictionary(trx); + for (pfs_os_file_t d : deleted) + os_file_close(d); + log_write_up_to(trx->commit_lsn, true); + if (trx != parent_trx) + trx->free(); + if (!fts) +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (!rollback_add_partition) +#endif + DBUG_RETURN(0); + purge_sys.resume_FTS(); + DBUG_RETURN(0); +} + +/** Rename an InnoDB table. +@param[in,out] trx InnoDB data dictionary transaction +@param[in] from old table name +@param[in] to new table name +@param[in] use_fk whether to enforce FOREIGN KEY +@return DB_SUCCESS or error code */ +static dberr_t innobase_rename_table(trx_t *trx, const char *from, + const char *to, bool use_fk) +{ + dberr_t error; + char norm_to[FN_REFLEN]; + char norm_from[FN_REFLEN]; + + DBUG_ENTER("innobase_rename_table"); + DBUG_ASSERT(trx->dict_operation); + + ut_ad(!srv_read_only_mode); + + normalize_table_name(norm_to, to); + normalize_table_name(norm_from, from); + + DEBUG_SYNC_C("innodb_rename_table_ready"); + + ut_ad(trx->will_lock); + + error = row_rename_table_for_mysql(norm_from, norm_to, trx, use_fk); + + if (error != DB_SUCCESS) { + if (error == DB_TABLE_NOT_FOUND + && lower_case_table_names == 1) { + char* is_part = is_partition(norm_from); + + if (is_part) { + char par_case_name[FN_REFLEN]; +#ifndef _WIN32 + /* Check for the table using lower + case name, including the partition + separator "P" */ + strcpy(par_case_name, norm_from); + innobase_casedn_str(par_case_name); +#else + /* On Windows platfrom, check + whether there exists table name in + system table whose name is + not being normalized to lower case */ + normalize_table_name_c_low( + par_case_name, from, false); +#endif /* _WIN32 */ + trx_start_if_not_started(trx, true); + error = row_rename_table_for_mysql( + par_case_name, norm_to, trx, false); + } + } + + if (error == DB_SUCCESS) { +#ifndef _WIN32 + sql_print_warning("Rename partition table %s" + " succeeds after converting to lower" + " case. The table may have" + " been moved from a case" + " in-sensitive file system.\n", + norm_from); +#else + sql_print_warning("Rename partition table %s" + " succeeds after skipping the step to" + " lower case the table name." + " The table may have been" + " moved from a case sensitive" + " file system.\n", + norm_from); +#endif /* _WIN32 */ + } + } + + DBUG_RETURN(error); +} + +/** TRUNCATE TABLE +@return error code +@retval 0 on success */ +int ha_innobase::truncate() +{ + mariadb_set_stats set_stats_temporary(handler_stats); + DBUG_ENTER("ha_innobase::truncate"); + + update_thd(); + + if (is_read_only()) + DBUG_RETURN(HA_ERR_TABLE_READONLY); + + HA_CREATE_INFO info; + dict_table_t *ib_table= m_prebuilt->table; + info.init(); + update_create_info_from_table(&info, table); + switch (dict_tf_get_rec_format(ib_table->flags)) { + case REC_FORMAT_REDUNDANT: + info.row_type= ROW_TYPE_REDUNDANT; + break; + case REC_FORMAT_COMPACT: + info.row_type= ROW_TYPE_COMPACT; + break; + case REC_FORMAT_COMPRESSED: + info.row_type= ROW_TYPE_COMPRESSED; + break; + case REC_FORMAT_DYNAMIC: + info.row_type= ROW_TYPE_DYNAMIC; + break; + } + + const auto stored_lock= m_prebuilt->stored_select_lock_type; + trx_t *trx= innobase_trx_allocate(m_user_thd); + trx_start_for_ddl(trx); + + if (ib_table->is_temporary()) + { + info.options|= HA_LEX_CREATE_TMP_TABLE; + btr_drop_temporary_table(*ib_table); + m_prebuilt->table= nullptr; + row_prebuilt_free(m_prebuilt); + m_prebuilt= nullptr; + my_free(m_upd_buf); + m_upd_buf= nullptr; + m_upd_buf_size= 0; + + row_mysql_lock_data_dictionary(trx); + ib_table->release(); + dict_sys.remove(ib_table, false, true); + int err= create(ib_table->name.m_name, table, &info, true, trx); + row_mysql_unlock_data_dictionary(trx); + + ut_ad(!err); + if (!err) + { + err= open(ib_table->name.m_name, 0, 0); + m_prebuilt->table->release(); + m_prebuilt->stored_select_lock_type= stored_lock; + } + + trx->free(); + +#ifdef BTR_CUR_HASH_ADAPT + if (UT_LIST_GET_LEN(ib_table->freed_indexes)) + { + ib_table->vc_templ= nullptr; + ib_table->id= 0; + } + else +#endif /* BTR_CUR_HASH_ADAPT */ + dict_mem_table_free(ib_table); + + DBUG_RETURN(err); + } + + mem_heap_t *heap= mem_heap_create(1000); + + if (!ib_table->space) + ib_senderrf(m_user_thd, IB_LOG_LEVEL_WARN, ER_TABLESPACE_DISCARDED, + table->s->table_name.str); + + dict_get_and_save_data_dir_path(ib_table); + info.data_file_name= ib_table->data_dir_path; + const char *temp_name= + dict_mem_create_temporary_tablename(heap, + ib_table->name.m_name, ib_table->id); + const char *name= mem_heap_strdup(heap, ib_table->name.m_name); + + dict_table_t *table_stats = nullptr, *index_stats = nullptr; + MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr; + + dberr_t error= DB_SUCCESS; + + dict_sys.freeze(SRW_LOCK_CALL); + for (const dict_foreign_t *f : ib_table->referenced_set) + if (dict_table_t *child= f->foreign_table) + if ((error= lock_table_for_trx(child, trx, LOCK_X)) != DB_SUCCESS) + break; + dict_sys.unfreeze(); + + if (error == DB_SUCCESS) + error= lock_table_for_trx(ib_table, trx, LOCK_X); + + const bool fts= error == DB_SUCCESS && + ib_table->flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS); + + if (fts) + { + fts_optimize_remove_table(ib_table); + purge_sys.stop_FTS(*ib_table); + error= fts_lock_tables(trx, *ib_table); + } + + /* Wait for purge threads to stop using the table. */ + for (uint n = 15; ib_table->get_ref_count() > 1; ) + { + if (!--n) + { + error= DB_LOCK_WAIT_TIMEOUT; + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + if (error == DB_SUCCESS && dict_stats_is_persistent_enabled(ib_table) && + !ib_table->is_stats_table()) + { + table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false, + DICT_ERR_IGNORE_NONE); + if (table_stats) + { + dict_sys.freeze(SRW_LOCK_CALL); + table_stats= dict_acquire_mdl_shared<false>(table_stats, m_user_thd, + &mdl_table); + dict_sys.unfreeze(); + } + index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false, + DICT_ERR_IGNORE_NONE); + if (index_stats) + { + dict_sys.freeze(SRW_LOCK_CALL); + index_stats= dict_acquire_mdl_shared<false>(index_stats, m_user_thd, + &mdl_index); + dict_sys.unfreeze(); + } + + if (table_stats && index_stats && + !strcmp(table_stats->name.m_name, TABLE_STATS_NAME) && + !strcmp(index_stats->name.m_name, INDEX_STATS_NAME) && + !(error= lock_table_for_trx(table_stats, trx, LOCK_X))) + error= lock_table_for_trx(index_stats, trx, LOCK_X); + } + + if (error == DB_SUCCESS) + error= lock_sys_tables(trx); + + std::vector<pfs_os_file_t> deleted; + + row_mysql_lock_data_dictionary(trx); + + if (error == DB_SUCCESS) + { + error= innobase_rename_table(trx, ib_table->name.m_name, temp_name, false); + if (error == DB_SUCCESS) + error= trx->drop_table(*ib_table); + } + + int err = convert_error_code_to_mysql(error, ib_table->flags, m_user_thd); + const auto update_time = ib_table->update_time; + + if (err) + { + trx_rollback_for_mysql(trx); + if (fts) + fts_optimize_add_table(ib_table); + } + else + { + const auto def_trx_id= ib_table->def_trx_id; + ib_table->release(); + m_prebuilt->table= nullptr; + + err= create(name, table, &info, dict_table_is_file_per_table(ib_table), + trx); + if (!err) + { + m_prebuilt->table->acquire(); + create_table_info_t::create_table_update_dict(m_prebuilt->table, + m_user_thd, info, *table); + trx->commit(deleted); + } + else + { + trx_rollback_for_mysql(trx); + m_prebuilt->table= dict_table_open_on_name(name, true, + DICT_ERR_IGNORE_FK_NOKEY); + m_prebuilt->table->def_trx_id= def_trx_id; + } + dict_names_t fk_tables; + dict_load_foreigns(m_prebuilt->table->name.m_name, nullptr, 1, true, + DICT_ERR_IGNORE_FK_NOKEY, fk_tables); + for (const char *f : fk_tables) + dict_sys.load_table({f, strlen(f)}); + } + + if (fts) + purge_sys.resume_FTS(); + + row_mysql_unlock_data_dictionary(trx); + for (pfs_os_file_t d : deleted) os_file_close(d); + + if (!err) + { + dict_stats_update(m_prebuilt->table, DICT_STATS_EMPTY_TABLE); + log_write_up_to(trx->commit_lsn, true); + row_prebuilt_t *prebuilt= m_prebuilt; + uchar *upd_buf= m_upd_buf; + ulint upd_buf_size= m_upd_buf_size; + /* Mimic ha_innobase::close(). */ + m_prebuilt= nullptr; + m_upd_buf= nullptr; + m_upd_buf_size= 0; + + err= open(name, 0, 0); + if (!err) + { + m_prebuilt->stored_select_lock_type= stored_lock; + m_prebuilt->table->update_time= update_time; + row_prebuilt_free(prebuilt); + my_free(upd_buf); + } + else + { + /* Revert to the old table. */ + m_prebuilt= prebuilt; + m_upd_buf= upd_buf; + m_upd_buf_size= upd_buf_size; + } + } + + trx->free(); + + mem_heap_free(heap); + + if (table_stats) + dict_table_close(table_stats, false, m_user_thd, mdl_table); + if (index_stats) + dict_table_close(index_stats, false, m_user_thd, mdl_index); + + DBUG_RETURN(err); +} + +/*********************************************************************//** +Renames an InnoDB table. +@return 0 or error code */ + +int +ha_innobase::rename_table( +/*======================*/ + const char* from, /*!< in: old name of the table */ + const char* to) /*!< in: new name of the table */ +{ + THD* thd = ha_thd(); + + DBUG_ENTER("ha_innobase::rename_table"); + + if (high_level_read_only) { + ib_senderrf(thd, IB_LOG_LEVEL_WARN, ER_READ_ONLY_MODE); + DBUG_RETURN(HA_ERR_TABLE_READONLY); + } + + trx_t* trx = innobase_trx_allocate(thd); + trx_start_for_ddl(trx); + + dict_table_t *table_stats = nullptr, *index_stats = nullptr; + MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr; + char norm_from[MAX_FULL_NAME_LEN]; + char norm_to[MAX_FULL_NAME_LEN]; + + normalize_table_name(norm_from, from); + normalize_table_name(norm_to, to); + + dberr_t error = DB_SUCCESS; + const bool from_temp = dict_table_t::is_temporary_name(norm_from); + + if (from_temp) { + /* There is no need to lock any FOREIGN KEY child tables. */ + } else if (dict_table_t *table = dict_table_open_on_name( + norm_from, false, DICT_ERR_IGNORE_FK_NOKEY)) { + dict_sys.freeze(SRW_LOCK_CALL); + for (const dict_foreign_t* f : table->referenced_set) { + if (dict_table_t* child = f->foreign_table) { + error = lock_table_for_trx(child, trx, LOCK_X); + if (error != DB_SUCCESS) { + break; + } + } + } + dict_sys.unfreeze(); + if (error == DB_SUCCESS) { + error = lock_table_for_trx(table, trx, LOCK_X); + } + table->release(); + } + + if (strcmp(norm_from, TABLE_STATS_NAME) + && strcmp(norm_from, INDEX_STATS_NAME) + && strcmp(norm_to, TABLE_STATS_NAME) + && strcmp(norm_to, INDEX_STATS_NAME)) { + table_stats = dict_table_open_on_name(TABLE_STATS_NAME, false, + DICT_ERR_IGNORE_NONE); + if (table_stats) { + dict_sys.freeze(SRW_LOCK_CALL); + table_stats = dict_acquire_mdl_shared<false>( + table_stats, thd, &mdl_table); + dict_sys.unfreeze(); + } + index_stats = dict_table_open_on_name(INDEX_STATS_NAME, false, + DICT_ERR_IGNORE_NONE); + if (index_stats) { + dict_sys.freeze(SRW_LOCK_CALL); + index_stats = dict_acquire_mdl_shared<false>( + index_stats, thd, &mdl_index); + dict_sys.unfreeze(); + } + + if (error == DB_SUCCESS && table_stats && index_stats + && !strcmp(table_stats->name.m_name, TABLE_STATS_NAME) + && !strcmp(index_stats->name.m_name, INDEX_STATS_NAME)) { + error = lock_table_for_trx(table_stats, trx, LOCK_X, + from_temp); + if (error == DB_SUCCESS) { + error = lock_table_for_trx(index_stats, trx, + LOCK_X, from_temp); + } + if (error != DB_SUCCESS && from_temp) { + ut_ad(error == DB_LOCK_WAIT); + ut_ad(trx->error_state == DB_SUCCESS); + error = DB_SUCCESS; + /* We may skip renaming statistics if + we cannot lock the tables, when the + table is being renamed from from a + temporary name. */ + dict_table_close(table_stats, false, thd, + mdl_table); + dict_table_close(index_stats, false, thd, + mdl_index); + table_stats = nullptr; + index_stats = nullptr; + } + } + } + + if (error == DB_SUCCESS) { + error = lock_table_for_trx(dict_sys.sys_tables, trx, LOCK_X); + if (error == DB_SUCCESS) { + error = lock_table_for_trx(dict_sys.sys_foreign, trx, + LOCK_X); + if (error == DB_SUCCESS) { + error = lock_table_for_trx( + dict_sys.sys_foreign_cols, + trx, LOCK_X); + } + } + } + + row_mysql_lock_data_dictionary(trx); + + if (error == DB_SUCCESS) { + error = innobase_rename_table(trx, from, to, true); + } + + DEBUG_SYNC(thd, "after_innobase_rename_table"); + + if (error == DB_SUCCESS && table_stats && index_stats) { + error = dict_stats_rename_table(norm_from, norm_to, trx); + if (error == DB_DUPLICATE_KEY) { + /* The duplicate may also occur in + mysql.innodb_index_stats. */ + my_error(ER_DUP_KEY, MYF(0), + "mysql.innodb_table_stats"); + error = DB_ERROR; + } + } + + if (error == DB_SUCCESS) { + trx->flush_log_later = true; + innobase_commit_low(trx); + } else { + trx->rollback(); + } + + if (table_stats) { + dict_table_close(table_stats, true, thd, mdl_table); + } + if (index_stats) { + dict_table_close(index_stats, true, thd, mdl_index); + } + row_mysql_unlock_data_dictionary(trx); + if (error == DB_SUCCESS) { + log_write_up_to(trx->commit_lsn, true); + } + trx->flush_log_later = false; + trx->free(); + + if (error == DB_DUPLICATE_KEY) { + /* We are not able to deal with handler::get_dup_key() + during DDL operations, because the duplicate key would + exist in metadata tables, not in the user table. */ + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), to); + error = DB_ERROR; + } else if (error == DB_LOCK_WAIT_TIMEOUT) { + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0), to); + error = DB_LOCK_WAIT; + } + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + +/*********************************************************************//** +Estimates the number of index records in a range. +@return estimated number of rows */ + +ha_rows +ha_innobase::records_in_range( +/*==========================*/ + uint keynr, /*!< in: index number */ + const key_range *min_key, /*!< in: start key value of the + range, may also be 0 */ + const key_range *max_key, /*!< in: range end key val, may + also be 0 */ + page_range *pages) +{ + KEY* key; + dict_index_t* index; + dtuple_t* range_start; + dtuple_t* range_end; + ha_rows n_rows; + page_cur_mode_t mode1; + page_cur_mode_t mode2; + mem_heap_t* heap; + + DBUG_ENTER("records_in_range"); + + ut_a(m_prebuilt->trx == thd_to_trx(ha_thd())); + + m_prebuilt->trx->op_info = "estimating records in index range"; + + active_index = keynr; + + key = table->key_info + active_index; + + index = innobase_get_index(keynr); + + /* There exists possibility of not being able to find requested + index due to inconsistency between MySQL and InoDB dictionary info. + Necessary message should have been printed in innobase_get_index() */ + if (!m_prebuilt->table->space) { + n_rows = HA_POS_ERROR; + goto func_exit; + } + if (!index) { + n_rows = HA_POS_ERROR; + goto func_exit; + } + if (index->is_corrupted()) { + n_rows = HA_ERR_INDEX_CORRUPT; + goto func_exit; + } + if (!row_merge_is_index_usable(m_prebuilt->trx, index)) { + n_rows = HA_ERR_TABLE_DEF_CHANGED; + goto func_exit; + } + + heap = mem_heap_create(2 * (key->ext_key_parts * sizeof(dfield_t) + + sizeof(dtuple_t))); + + range_start = dtuple_create(heap, key->ext_key_parts); + dict_index_copy_types(range_start, index, key->ext_key_parts); + + range_end = dtuple_create(heap, key->ext_key_parts); + dict_index_copy_types(range_end, index, key->ext_key_parts); + + row_sel_convert_mysql_key_to_innobase( + range_start, + m_prebuilt->srch_key_val1, + m_prebuilt->srch_key_val_len, + index, + (byte*) (min_key ? min_key->key : (const uchar*) 0), + (ulint) (min_key ? min_key->length : 0)); + + DBUG_ASSERT(min_key + ? range_start->n_fields > 0 + : range_start->n_fields == 0); + + row_sel_convert_mysql_key_to_innobase( + range_end, + m_prebuilt->srch_key_val2, + m_prebuilt->srch_key_val_len, + index, + (byte*) (max_key ? max_key->key : (const uchar*) 0), + (ulint) (max_key ? max_key->length : 0)); + + DBUG_ASSERT(max_key + ? range_end->n_fields > 0 + : range_end->n_fields == 0); + + mode1 = convert_search_mode_to_innobase( + min_key ? min_key->flag : HA_READ_KEY_EXACT); + + mode2 = convert_search_mode_to_innobase( + max_key ? max_key->flag : HA_READ_KEY_EXACT); + + if (mode1 != PAGE_CUR_UNSUPP && mode2 != PAGE_CUR_UNSUPP) { + + if (dict_index_is_spatial(index)) { + /*Only min_key used in spatial index. */ + n_rows = rtr_estimate_n_rows_in_range( + index, range_start, mode1); + } else { + btr_pos_t tuple1(range_start, mode1, pages->first_page); + btr_pos_t tuple2(range_end, mode2, pages->last_page); + n_rows = btr_estimate_n_rows_in_range( + index, &tuple1, &tuple2); + pages->first_page= tuple1.page_id.raw(); + pages->last_page= tuple2.page_id.raw(); + } + } else { + + n_rows = HA_POS_ERROR; + } + + mem_heap_free(heap); + + DBUG_EXECUTE_IF( + "print_btr_estimate_n_rows_in_range_return_value", + push_warning_printf( + ha_thd(), Sql_condition::WARN_LEVEL_WARN, + ER_NO_DEFAULT, + "btr_estimate_n_rows_in_range(): %lld", + (longlong) n_rows); + ); + +func_exit: + + m_prebuilt->trx->op_info = (char*)""; + + /* The MySQL optimizer seems to believe an estimate of 0 rows is + always accurate and may return the result 'Empty set' based on that. + The accuracy is not guaranteed, and even if it were, for a locking + read we should anyway perform the search to set the next-key lock. + Add 1 to the value to make sure MySQL does not make the assumption! */ + + if (n_rows == 0) { + n_rows = 1; + } + + DBUG_RETURN((ha_rows) n_rows); +} + +/*********************************************************************//** +Gives an UPPER BOUND to the number of rows in a table. This is used in +filesort.cc. +@return upper bound of rows */ + +ha_rows +ha_innobase::estimate_rows_upper_bound() +/*====================================*/ +{ + const dict_index_t* index; + ulonglong estimate; + ulonglong local_data_file_length; + mariadb_set_stats set_stats_temporary(handler_stats); + DBUG_ENTER("estimate_rows_upper_bound"); + + /* We do not know if MySQL can call this function before calling + external_lock(). To be safe, update the thd of the current table + handle. */ + + update_thd(ha_thd()); + + m_prebuilt->trx->op_info = "calculating upper bound for table rows"; + + index = dict_table_get_first_index(m_prebuilt->table); + + ulint stat_n_leaf_pages = index->stat_n_leaf_pages; + + ut_a(stat_n_leaf_pages > 0); + + local_data_file_length = ulonglong(stat_n_leaf_pages) + << srv_page_size_shift; + + /* Calculate a minimum length for a clustered index record and from + that an upper bound for the number of rows. Since we only calculate + new statistics in row0mysql.cc when a table has grown by a threshold + factor, we must add a safety factor 2 in front of the formula below. */ + + estimate = 2 * local_data_file_length + / dict_index_calc_min_rec_len(index); + + m_prebuilt->trx->op_info = ""; + + /* Set num_rows less than MERGEBUFF to simulate the case where we do + not have enough space to merge the externally sorted file blocks. */ + DBUG_EXECUTE_IF("set_num_rows_lt_MERGEBUFF", + estimate = 2; + DBUG_SET("-d,set_num_rows_lt_MERGEBUFF"); + ); + + DBUG_RETURN((ha_rows) estimate); +} + +/*********************************************************************//** +How many seeks it will take to read through the table. This is to be +comparable to the number returned by records_in_range so that we can +decide if we should scan the table or use keys. +@return estimated time measured in disk seeks */ + +double +ha_innobase::scan_time() +/*====================*/ +{ + /* Since MySQL seems to favor table scans too much over index + searches, we pretend that a sequential read takes the same time + as a random disk read, that is, we do not divide the following + by 10, which would be physically realistic. */ + + /* The locking below is disabled for performance reasons. Without + it we could end up returning uninitialized value to the caller, + which in the worst case could make some query plan go bogus or + issue a Valgrind warning. */ + if (m_prebuilt == NULL) { + /* In case of derived table, Optimizer will try to fetch stat + for table even before table is create or open. In such + cases return default value of 1. + TODO: This will be further improved to return some approximate + estimate but that would also needs pre-population of stats + structure. As of now approach is in sync with MyISAM. */ + return(ulonglong2double(stats.data_file_length) / IO_SIZE + 2); + } + + ulint stat_clustered_index_size; + + ut_a(m_prebuilt->table->stat_initialized); + + stat_clustered_index_size = + m_prebuilt->table->stat_clustered_index_size; + + return((double) stat_clustered_index_size); +} + +/******************************************************************//** +Calculate the time it takes to read a set of ranges through an index +This enables us to optimise reads for clustered indexes. +@return estimated time measured in disk seeks */ + +double +ha_innobase::read_time( +/*===================*/ + uint index, /*!< in: key number */ + uint ranges, /*!< in: how many ranges */ + ha_rows rows) /*!< in: estimated number of rows in the ranges */ +{ + ha_rows total_rows; + + if (index != table->s->primary_key) { + /* Not clustered */ + return(handler::read_time(index, ranges, rows)); + } + + /* Assume that the read time is proportional to the scan time for all + rows + at most one seek per range. */ + + double time_for_scan = scan_time(); + + if ((total_rows = estimate_rows_upper_bound()) < rows) { + + return(time_for_scan); + } + + return(ranges + (double) rows / (double) total_rows * time_for_scan); +} + +/*********************************************************************//** +Calculates the key number used inside MySQL for an Innobase index. +@return the key number used inside MySQL */ +static +unsigned +innobase_get_mysql_key_number_for_index( +/*====================================*/ + const TABLE* table, /*!< in: table in MySQL data + dictionary */ + dict_table_t* ib_table,/*!< in: table in InnoDB data + dictionary */ + const dict_index_t* index) /*!< in: index */ +{ + const dict_index_t* ind; + unsigned int i; + + /* If index does not belong to the table object of share structure + (ib_table comes from the share structure) search the index->table + object instead */ + if (index->table != ib_table) { + i = 0; + ind = dict_table_get_first_index(index->table); + + while (index != ind) { + ind = dict_table_get_next_index(ind); + i++; + } + + if (dict_index_is_auto_gen_clust(index)) { + ut_a(i > 0); + i--; + } + + return(i); + } + + /* Directly find matching index with information from mysql TABLE + structure and InnoDB dict_index_t list */ + for (i = 0; i < table->s->keys; i++) { + ind = dict_table_get_index_on_name( + ib_table, table->key_info[i].name.str); + + if (index == ind) { + return(i); + } + } + + /* Loop through each index of the table and lock them */ + for (ind = dict_table_get_first_index(ib_table); + ind != NULL; + ind = dict_table_get_next_index(ind)) { + if (index == ind) { + /* Temp index is internal to InnoDB, that is + not present in the MySQL index list, so no + need to print such mismatch warning. */ + if (index->is_committed()) { + sql_print_warning( + "Found index %s in InnoDB index list" + " but not its MariaDB index number." + " It could be an InnoDB internal" + " index.", + index->name()); + } + return(~0U); + } + } + + ut_error; + + return(~0U); +} + +/*********************************************************************//** +Calculate Record Per Key value. Need to exclude the NULL value if +innodb_stats_method is set to "nulls_ignored" +@return estimated record per key value */ +rec_per_key_t +innodb_rec_per_key( +/*===============*/ + dict_index_t* index, /*!< in: dict_index_t structure */ + ulint i, /*!< in: the column we are + calculating rec per key */ + ha_rows records) /*!< in: estimated total records */ +{ + rec_per_key_t rec_per_key; + ib_uint64_t n_diff; + + ut_a(index->table->stat_initialized); + + ut_ad(i < dict_index_get_n_unique(index)); + ut_ad(!dict_index_is_spatial(index)); + + if (records == 0) { + /* "Records per key" is meaningless for empty tables. + Return 1.0 because that is most convenient to the Optimizer. */ + return(1.0); + } + + n_diff = index->stat_n_diff_key_vals[i]; + + if (n_diff == 0) { + + rec_per_key = static_cast<rec_per_key_t>(records); + } else if (srv_innodb_stats_method == SRV_STATS_NULLS_IGNORED) { + ib_uint64_t n_null; + ib_uint64_t n_non_null; + + n_non_null = index->stat_n_non_null_key_vals[i]; + + /* In theory, index->stat_n_non_null_key_vals[i] + should always be less than the number of records. + Since this is statistics value, the value could + have slight discrepancy. But we will make sure + the number of null values is not a negative number. */ + if (records < n_non_null) { + n_null = 0; + } else { + n_null = records - n_non_null; + } + + /* If the number of NULL values is the same as or + larger than that of the distinct values, we could + consider that the table consists mostly of NULL value. + Set rec_per_key to 1. */ + if (n_diff <= n_null) { + rec_per_key = 1.0; + } else { + /* Need to exclude rows with NULL values from + rec_per_key calculation */ + rec_per_key + = static_cast<rec_per_key_t>(records - n_null) + / static_cast<rec_per_key_t>(n_diff - n_null); + } + } else { + DEBUG_SYNC_C("after_checking_for_0"); + rec_per_key = static_cast<rec_per_key_t>(records) + / static_cast<rec_per_key_t>(n_diff); + } + + if (rec_per_key < 1.0) { + /* Values below 1.0 are meaningless and must be due to the + stats being imprecise. */ + rec_per_key = 1.0; + } + + return(rec_per_key); +} + +/** Calculate how many KiB of new data we will be able to insert to the +tablespace without running out of space. Start with a space object that has +been acquired by the caller who holds it for the calculation, +@param[in] space tablespace object from fil_space_acquire() +@return available space in KiB */ +static uintmax_t +fsp_get_available_space_in_free_extents(const fil_space_t& space) +{ + ulint size_in_header = space.size_in_header; + if (size_in_header < FSP_EXTENT_SIZE) { + return 0; /* TODO: count free frag pages and + return a value based on that */ + } + + /* Below we play safe when counting free extents above the free limit: + some of them will contain extent descriptor pages, and therefore + will not be free extents */ + ut_ad(size_in_header >= space.free_limit); + ulint n_free_up = + (size_in_header - space.free_limit) / FSP_EXTENT_SIZE; + + const ulint size = space.physical_size(); + if (n_free_up > 0) { + n_free_up--; + n_free_up -= n_free_up / (size / FSP_EXTENT_SIZE); + } + + /* We reserve 1 extent + 0.5 % of the space size to undo logs + and 1 extent + 0.5 % to cleaning operations; NOTE: this source + code is duplicated in the function above! */ + + ulint reserve = 2 + ((size_in_header / FSP_EXTENT_SIZE) * 2) / 200; + ulint n_free = space.free_len + n_free_up; + + if (reserve > n_free) { + return(0); + } + + return(static_cast<uintmax_t>(n_free - reserve) + * FSP_EXTENT_SIZE * (size / 1024)); +} + +/*********************************************************************//** +Returns statistics information of the table to the MySQL interpreter, +in various fields of the handle object. +@return HA_ERR_* error code or 0 */ + +int +ha_innobase::info_low( +/*==================*/ + uint flag, /*!< in: what information is requested */ + bool is_analyze) +{ + dict_table_t* ib_table; + ib_uint64_t n_rows; + char path[FN_REFLEN]; + os_file_stat_t stat_info; + + DBUG_ENTER("info"); + + DEBUG_SYNC_C("ha_innobase_info_low"); + + /* If we are forcing recovery at a high level, we will suppress + statistics calculation on tables, because that may crash the + server if an index is badly corrupted. */ + + /* We do not know if MySQL can call this function before calling + external_lock(). To be safe, update the thd of the current table + handle. */ + + update_thd(ha_thd()); + + m_prebuilt->trx->op_info = "returning various info to MariaDB"; + + ib_table = m_prebuilt->table; + DBUG_ASSERT(ib_table->get_ref_count() > 0); + + if (!ib_table->is_readable()) { + ib_table->stats_mutex_lock(); + ib_table->stat_initialized = true; + ib_table->stat_n_rows = 0; + ib_table->stat_clustered_index_size = 0; + ib_table->stat_sum_of_other_index_sizes = 0; + ib_table->stats_mutex_unlock(); + } + + if (flag & HA_STATUS_TIME) { + if (is_analyze || innobase_stats_on_metadata) { + + dict_stats_upd_option_t opt; + dberr_t ret; + + m_prebuilt->trx->op_info = "updating table statistics"; + + if (dict_stats_is_persistent_enabled(ib_table)) { + if (is_analyze) { + if (!srv_read_only_mode) { + dict_stats_recalc_pool_del( + ib_table->id, false); + } + opt = DICT_STATS_RECALC_PERSISTENT; + } else { + /* This is e.g. 'SHOW INDEXES', fetch + the persistent stats from disk. */ + opt = DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY; + } + } else { + opt = DICT_STATS_RECALC_TRANSIENT; + } + + ret = dict_stats_update(ib_table, opt); + + if (ret != DB_SUCCESS) { + m_prebuilt->trx->op_info = ""; + DBUG_RETURN(HA_ERR_GENERIC); + } + + m_prebuilt->trx->op_info = + "returning various info to MariaDB"; + } + + + stats.update_time = (ulong) ib_table->update_time; + } + + dict_stats_init(ib_table); + + if (flag & HA_STATUS_VARIABLE) { + + ulint stat_clustered_index_size; + ulint stat_sum_of_other_index_sizes; + + ib_table->stats_mutex_lock(); + + ut_a(ib_table->stat_initialized); + + n_rows = ib_table->stat_n_rows; + + stat_clustered_index_size + = ib_table->stat_clustered_index_size; + + stat_sum_of_other_index_sizes + = ib_table->stat_sum_of_other_index_sizes; + + ib_table->stats_mutex_unlock(); + + /* + The MySQL optimizer seems to assume in a left join that n_rows + is an accurate estimate if it is zero. Of course, it is not, + since we do not have any locks on the rows yet at this phase. + Since SHOW TABLE STATUS seems to call this function with the + HA_STATUS_TIME flag set, while the left join optimizer does not + set that flag, we add one to a zero value if the flag is not + set. That way SHOW TABLE STATUS will show the best estimate, + while the optimizer never sees the table empty. */ + + if (n_rows == 0 && !(flag & (HA_STATUS_TIME | HA_STATUS_OPEN))) { + n_rows++; + } + + /* Fix bug#40386: Not flushing query cache after truncate. + n_rows can not be 0 unless the table is empty, set to 1 + instead. The original problem of bug#29507 is actually + fixed in the server code. */ + if (thd_sql_command(m_user_thd) == SQLCOM_TRUNCATE) { + + n_rows = 1; + + /* We need to reset the m_prebuilt value too, otherwise + checks for values greater than the last value written + to the table will fail and the autoinc counter will + not be updated. This will force write_row() into + attempting an update of the table's AUTOINC counter. */ + + m_prebuilt->autoinc_last_value = 0; + } + + stats.records = (ha_rows) n_rows; + stats.deleted = 0; + if (fil_space_t* space = ib_table->space) { + const ulint size = space->physical_size(); + stats.data_file_length + = ulonglong(stat_clustered_index_size) + * size; + stats.index_file_length + = ulonglong(stat_sum_of_other_index_sizes) + * size; + space->s_lock(); + stats.delete_length = 1024 + * fsp_get_available_space_in_free_extents( + *space); + space->s_unlock(); + } + stats.check_time = 0; + stats.mrr_length_per_rec= (uint)ref_length + 8; // 8 = max(sizeof(void *)); + + if (stats.records == 0) { + stats.mean_rec_length = 0; + } else { + stats.mean_rec_length = (ulong) + (stats.data_file_length / stats.records); + } + } + + if (flag & HA_STATUS_CONST) { + /* Verify the number of index in InnoDB and MySQL + matches up. If m_prebuilt->clust_index_was_generated + holds, InnoDB defines GEN_CLUST_INDEX internally */ + ulint num_innodb_index = UT_LIST_GET_LEN(ib_table->indexes) + - m_prebuilt->clust_index_was_generated; + if (table->s->keys < num_innodb_index) { + /* If there are too many indexes defined + inside InnoDB, ignore those that are being + created, because MySQL will only consider + the fully built indexes here. */ + + for (const dict_index_t* index + = UT_LIST_GET_FIRST(ib_table->indexes); + index != NULL; + index = UT_LIST_GET_NEXT(indexes, index)) { + + /* First, online index creation is + completed inside InnoDB, and then + MySQL attempts to upgrade the + meta-data lock so that it can rebuild + the .frm file. If we get here in that + time frame, dict_index_is_online_ddl() + would not hold and the index would + still not be included in TABLE_SHARE. */ + if (!index->is_committed()) { + num_innodb_index--; + } + } + + if (table->s->keys < num_innodb_index + && innobase_fts_check_doc_id_index( + ib_table, NULL, NULL) + == FTS_EXIST_DOC_ID_INDEX) { + num_innodb_index--; + } + } + + if (table->s->keys != num_innodb_index) { + ib_table->dict_frm_mismatch = DICT_FRM_INCONSISTENT_KEYS; + ib_push_frm_error(m_user_thd, ib_table, table, num_innodb_index, true); + } + + snprintf(path, sizeof(path), "%s/%s%s", + mysql_data_home, table->s->normalized_path.str, + reg_ext); + + unpack_filename(path,path); + + /* Note that we do not know the access time of the table, + nor the CHECK TABLE time, nor the UPDATE or INSERT time. */ + + if (os_file_get_status( + path, &stat_info, false, + srv_read_only_mode) == DB_SUCCESS) { + stats.create_time = (ulong) stat_info.ctime; + } + + ib_table->stats_mutex_lock(); + auto _ = make_scope_exit([ib_table]() { + ib_table->stats_mutex_unlock(); }); + + ut_a(ib_table->stat_initialized); + + for (uint i = 0; i < table->s->keys; i++) { + ulong j; + + dict_index_t* index = innobase_get_index(i); + + if (index == NULL) { + ib_table->dict_frm_mismatch = DICT_FRM_INCONSISTENT_KEYS; + ib_push_frm_error(m_user_thd, ib_table, table, num_innodb_index, true); + break; + } + + KEY* key = &table->key_info[i]; + + for (j = 0; j < key->ext_key_parts; j++) { + + if ((key->flags & HA_FULLTEXT) + || (key->flags & HA_SPATIAL)) { + + /* The record per key does not apply to + FTS or Spatial indexes. */ + /* + key->rec_per_key[j] = 1; + key->set_records_per_key(j, 1.0); + */ + continue; + } + + if (j + 1 > index->n_uniq) { + sql_print_error( + "Index %s of %s has %u columns" + " unique inside InnoDB, but " + "server is asking statistics for" + " %lu columns. Have you mixed " + "up .frm files from different " + " installations? %s", + index->name(), + ib_table->name.m_name, + index->n_uniq, j + 1, + TROUBLESHOOTING_MSG); + break; + } + + /* innodb_rec_per_key() will use + index->stat_n_diff_key_vals[] and the value we + pass index->table->stat_n_rows. Both are + calculated by ANALYZE and by the background + stats gathering thread (which kicks in when too + much of the table has been changed). In + addition table->stat_n_rows is adjusted with + each DML (e.g. ++ on row insert). Those + adjustments are not MVCC'ed and not even + reversed on rollback. So, + index->stat_n_diff_key_vals[] and + index->table->stat_n_rows could have been + calculated at different time. This is + acceptable. */ + + ulong rec_per_key_int = static_cast<ulong>( + innodb_rec_per_key(index, j, + stats.records)); + + /* Since MySQL seems to favor table scans + too much over index searches, we pretend + index selectivity is 2 times better than + our estimate: */ + + rec_per_key_int = rec_per_key_int / 2; + + if (rec_per_key_int == 0) { + rec_per_key_int = 1; + } + + key->rec_per_key[j] = rec_per_key_int; + } + } + } + + if (srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN) { + + goto func_exit; + + } else if (flag & HA_STATUS_ERRKEY) { + const dict_index_t* err_index; + + ut_a(m_prebuilt->trx); + ut_a(m_prebuilt->trx->magic_n == TRX_MAGIC_N); + + err_index = trx_get_error_info(m_prebuilt->trx); + + if (err_index) { + errkey = innobase_get_mysql_key_number_for_index( + table, ib_table, err_index); + } else { + errkey = (unsigned int) ( + (m_prebuilt->trx->error_key_num + == ULINT_UNDEFINED) + ? ~0U + : m_prebuilt->trx->error_key_num); + } + } + + if ((flag & HA_STATUS_AUTO) && table->found_next_number_field) { + stats.auto_increment_value = innobase_peek_autoinc(); + } + +func_exit: + m_prebuilt->trx->op_info = (char*)""; + + DBUG_RETURN(0); +} + +/*********************************************************************//** +Returns statistics information of the table to the MySQL interpreter, +in various fields of the handle object. +@return HA_ERR_* error code or 0 */ + +int +ha_innobase::info( +/*==============*/ + uint flag) /*!< in: what information is requested */ +{ + return(info_low(flag, false /* not ANALYZE */)); +} + +/* +Updates index cardinalities of the table, based on random dives into +each index tree. This does NOT calculate exact statistics on the table. +@return HA_ADMIN_* error code or HA_ADMIN_OK */ + +int +ha_innobase::analyze(THD*, HA_CHECK_OPT*) +{ + /* Simply call info_low() with all the flags + and request recalculation of the statistics */ + int ret = info_low( + HA_STATUS_TIME | HA_STATUS_CONST | HA_STATUS_VARIABLE, + true /* this is ANALYZE */); + + if (ret != 0) { + return(HA_ADMIN_FAILED); + } + + return(HA_ADMIN_OK); +} + +/*****************************************************************//** +Defragment table. +@return error number */ +inline int ha_innobase::defragment_table() +{ + for (dict_index_t *index= dict_table_get_first_index(m_prebuilt->table); + index; index= dict_table_get_next_index(index)) + { + if (!index->is_btree()) + continue; + + if (btr_defragment_find_index(index)) + { + // We borrow this error code. When the same index is already in + // the defragmentation queue, issuing another defragmentation + // only introduces overhead. We return an error here to let the + // user know this is not necessary. Note that this will fail a + // query that's trying to defragment a full table if one of the + // indicies in that table is already in defragmentation. We + // choose this behavior so user is aware of this rather than + // silently defragment other indicies of that table. + return ER_SP_ALREADY_EXISTS; + } + + btr_pcur_t pcur; + + mtr_t mtr; + mtr.start(); + if (dberr_t err= pcur.open_leaf(true, index, BTR_SEARCH_LEAF, &mtr)) + { + mtr.commit(); + return convert_error_code_to_mysql(err, 0, m_user_thd); + } + else if (btr_pcur_get_block(&pcur)->page.id().page_no() == index->page) + { + mtr.commit(); + continue; + } + + btr_pcur_move_to_next(&pcur, &mtr); + btr_pcur_store_position(&pcur, &mtr); + mtr.commit(); + ut_ad(pcur.index() == index); + const bool interrupted= btr_defragment_add_index(&pcur, m_user_thd); + ut_free(pcur.old_rec_buf); + if (interrupted) + return ER_QUERY_INTERRUPTED; + } + + return 0; +} + +/**********************************************************************//** +This is mapped to "ALTER TABLE tablename ENGINE=InnoDB", which rebuilds +the table in MySQL. */ + +int +ha_innobase::optimize( +/*==================*/ + THD* thd, /*!< in: connection thread handle */ + HA_CHECK_OPT*) +{ + + /* FTS-FIXME: Since MySQL doesn't support engine-specific commands, + we have to hijack some existing command in order to be able to test + the new admin commands added in InnoDB's FTS support. For now, we + use MySQL's OPTIMIZE command, normally mapped to ALTER TABLE in + InnoDB (so it recreates the table anew), and map it to OPTIMIZE. + + This works OK otherwise, but MySQL locks the entire table during + calls to OPTIMIZE, which is undesirable. */ + bool try_alter = true; + + if (!m_prebuilt->table->is_temporary() + && m_prebuilt->table->is_readable() + && srv_defragment) { + int err = defragment_table(); + + if (err == 0) { + try_alter = false; + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + uint(err), + "InnoDB: Cannot defragment table %s: returned error code %d\n", + m_prebuilt->table->name.m_name, err); + + if(err == ER_SP_ALREADY_EXISTS) { + try_alter = false; + } + } + } + + if (innodb_optimize_fulltext_only) { + if (m_prebuilt->table->fts && m_prebuilt->table->fts->cache + && m_prebuilt->table->space) { + fts_sync_table(m_prebuilt->table); + fts_optimize_table(m_prebuilt->table); + } + try_alter = false; + } + + return try_alter ? HA_ADMIN_TRY_ALTER : HA_ADMIN_OK; +} + +/*******************************************************************//** +Tries to check that an InnoDB table is not corrupted. If corruption is +noticed, prints to stderr information about it. In case of corruption +may also assert a failure and crash the server. +@return HA_ADMIN_CORRUPT or HA_ADMIN_OK */ + +int +ha_innobase::check( +/*===============*/ + THD* thd, /*!< in: user thread handle */ + HA_CHECK_OPT* check_opt) /*!< in: check options */ +{ + ulint n_rows; + ulint n_rows_in_table = ULINT_UNDEFINED; + bool is_ok = true; + dberr_t ret; + + DBUG_ENTER("ha_innobase::check"); + DBUG_ASSERT(thd == ha_thd()); + DBUG_ASSERT(thd == m_user_thd); + ut_a(m_prebuilt->trx->magic_n == TRX_MAGIC_N); + ut_a(m_prebuilt->trx == thd_to_trx(thd)); + ut_ad(m_prebuilt->trx->mysql_thd == thd); + + if (m_prebuilt->mysql_template == NULL) { + /* Build the template; we will use a dummy template + in index scans done in checking */ + + build_template(true); + } + + if (!m_prebuilt->table->space) { + ib_senderrf( + thd, + IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_DISCARDED, + table->s->table_name.str); + + DBUG_RETURN(HA_ADMIN_CORRUPT); + } else if (!m_prebuilt->table->is_readable()) { + ib_senderrf( + thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_MISSING, + table->s->table_name.str); + + DBUG_RETURN(HA_ADMIN_CORRUPT); + } + + m_prebuilt->trx->op_info = "checking table"; + + uint old_isolation_level = m_prebuilt->trx->isolation_level; + + /* We must run the index record counts at an isolation level + >= READ COMMITTED, because a dirty read can see a wrong number + of records in some index; to play safe, we normally use + REPEATABLE READ here */ + m_prebuilt->trx->isolation_level = high_level_read_only + && !m_prebuilt->table->is_temporary() + ? TRX_ISO_READ_UNCOMMITTED + : TRX_ISO_REPEATABLE_READ; + + trx_start_if_not_started(m_prebuilt->trx, false); + m_prebuilt->trx->read_view.open(m_prebuilt->trx); + + for (dict_index_t* index + = dict_table_get_first_index(m_prebuilt->table); + index; + index = dict_table_get_next_index(index)) { + /* If this is an index being created or dropped, skip */ + if (!index->is_committed()) { + continue; + } + if (index->type & DICT_FTS) { + /* We do not check any FULLTEXT INDEX. */ + continue; + } + + if ((check_opt->flags & T_QUICK) || index->is_corrupted()) { + } else if (trx_id_t bulk_trx_id = + m_prebuilt->table->bulk_trx_id) { + if (!m_prebuilt->trx->read_view.changes_visible( + bulk_trx_id)) { + is_ok = true; + goto func_exit; + } + + if (btr_validate_index(index, m_prebuilt->trx) + != DB_SUCCESS) { + is_ok = false; + push_warning_printf( + thd, + Sql_condition::WARN_LEVEL_WARN, + ER_NOT_KEYFILE, + "InnoDB: The B-tree of" + " index %s is corrupted.", + index->name()); + continue; + } + } + + /* Instead of invoking change_active_index(), set up + a dummy template for non-locking reads, disabling + access to the clustered index. */ + m_prebuilt->index = index; + + m_prebuilt->index_usable = row_merge_is_index_usable( + m_prebuilt->trx, m_prebuilt->index); + + DBUG_EXECUTE_IF( + "dict_set_index_corrupted", + if (!index->is_primary()) { + m_prebuilt->index_usable = FALSE; + dict_set_corrupted(index, + "dict_set_index_corrupted"); + }); + + if (UNIV_UNLIKELY(!m_prebuilt->index_usable)) { + if (index->is_corrupted()) { + push_warning_printf( + thd, + Sql_condition::WARN_LEVEL_WARN, + HA_ERR_INDEX_CORRUPT, + "InnoDB: Index %s is marked as" + " corrupted", + index->name()); + is_ok = false; + } else { + push_warning_printf( + thd, + Sql_condition::WARN_LEVEL_WARN, + HA_ERR_TABLE_DEF_CHANGED, + "InnoDB: Insufficient history for" + " index %s", + index->name()); + } + continue; + } + + m_prebuilt->sql_stat_start = TRUE; + m_prebuilt->template_type = ROW_MYSQL_DUMMY_TEMPLATE; + m_prebuilt->n_template = 0; + m_prebuilt->read_just_key = 0; + m_prebuilt->autoinc_error = DB_SUCCESS; + m_prebuilt->need_to_access_clustered = + !!(check_opt->flags & T_EXTEND); + + dtuple_set_n_fields(m_prebuilt->search_tuple, 0); + + m_prebuilt->select_lock_type = LOCK_NONE; + + /* Scan this index. */ + if (index->is_spatial()) { + ret = row_count_rtree_recs(m_prebuilt, &n_rows); + } else if (index->type & DICT_FTS) { + ret = DB_SUCCESS; + } else { + ret = row_check_index(m_prebuilt, &n_rows); + } + + DBUG_EXECUTE_IF( + "dict_set_index_corrupted", + if (!index->is_primary()) { + ret = DB_CORRUPTION; + }); + + if (ret == DB_INTERRUPTED || thd_killed(thd)) { + /* Do not report error since this could happen + during shutdown */ + break; + } + + if (ret == DB_SUCCESS + && m_prebuilt->autoinc_error != DB_MISSING_HISTORY) { + /* See if any non-fatal errors were reported. */ + ret = m_prebuilt->autoinc_error; + } + + if (ret != DB_SUCCESS) { + /* Assume some kind of corruption. */ + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_NOT_KEYFILE, + "InnoDB: The B-tree of" + " index %s is corrupted.", + index->name()); + is_ok = false; + dict_set_corrupted(index, "CHECK TABLE-check index"); + } + + + if (index == dict_table_get_first_index(m_prebuilt->table)) { + n_rows_in_table = n_rows; + } else if (!(index->type & DICT_FTS) + && (n_rows != n_rows_in_table)) { + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_NOT_KEYFILE, + "InnoDB: Index '%-.200s' contains " ULINTPF + " entries, should be " ULINTPF ".", + index->name(), n_rows, n_rows_in_table); + is_ok = false; + dict_set_corrupted(index, "CHECK TABLE; Wrong count"); + } + } + + /* Restore the original isolation level */ + m_prebuilt->trx->isolation_level = old_isolation_level; +#ifdef BTR_CUR_HASH_ADAPT +# if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + /* We validate the whole adaptive hash index for all tables + at every CHECK TABLE only when QUICK flag is not present. */ + + if (!(check_opt->flags & T_QUICK) + && !btr_search_validate(m_prebuilt->trx->mysql_thd)) { + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NOT_KEYFILE, + "InnoDB: The adaptive hash index is corrupted."); + is_ok = false; + } +# endif /* defined UNIV_AHI_DEBUG || defined UNIV_DEBUG */ +#endif /* BTR_CUR_HASH_ADAPT */ +func_exit: + m_prebuilt->trx->op_info = ""; + + DBUG_RETURN(is_ok ? HA_ADMIN_OK : HA_ADMIN_CORRUPT); +} + +/*******************************************************************//** +Gets the foreign key create info for a table stored in InnoDB. +@return own: character string in the form which can be inserted to the +CREATE TABLE statement, MUST be freed with +ha_innobase::free_foreign_key_create_info */ + +char* +ha_innobase::get_foreign_key_create_info(void) +/*==========================================*/ +{ + ut_a(m_prebuilt != NULL); + + /* We do not know if MySQL can call this function before calling + external_lock(). To be safe, update the thd of the current table + handle. */ + + update_thd(ha_thd()); + + m_prebuilt->trx->op_info = "getting info on foreign keys"; + + /* Output the data to a temporary string */ + std::string str = dict_print_info_on_foreign_keys( + TRUE, m_prebuilt->trx, + m_prebuilt->table); + + m_prebuilt->trx->op_info = ""; + + /* Allocate buffer for the string */ + char *fk_str = reinterpret_cast<char*>( + my_malloc(PSI_INSTRUMENT_ME, str.length() + 1, MYF(0))); + + if (fk_str) { + memcpy(fk_str, str.c_str(), str.length()); + fk_str[str.length()]='\0'; + } + + return(fk_str); +} + + +/***********************************************************************//** +Maps a InnoDB foreign key constraint to a equivalent MySQL foreign key info. +@return pointer to foreign key info */ +static +FOREIGN_KEY_INFO* +get_foreign_key_info( +/*=================*/ + THD* thd, /*!< in: user thread handle */ + dict_foreign_t* foreign)/*!< in: foreign key constraint */ +{ + FOREIGN_KEY_INFO f_key_info; + FOREIGN_KEY_INFO* pf_key_info; + uint i = 0; + size_t len; + char tmp_buff[NAME_LEN+1]; + char name_buff[NAME_LEN+1]; + const char* ptr; + LEX_CSTRING* referenced_key_name; + LEX_CSTRING* name = NULL; + + if (dict_table_t::is_temporary_name(foreign->foreign_table_name)) { + return NULL; + } + + ptr = dict_remove_db_name(foreign->id); + f_key_info.foreign_id = thd_make_lex_string( + thd, 0, ptr, strlen(ptr), 1); + + /* Name format: database name, '/', table name, '\0' */ + + /* Referenced (parent) database name */ + len = dict_get_db_name_len(foreign->referenced_table_name); + ut_a(len < sizeof(tmp_buff)); + memcpy(tmp_buff, foreign->referenced_table_name, len); + tmp_buff[len] = 0; + + len = filename_to_tablename(tmp_buff, name_buff, sizeof(name_buff)); + f_key_info.referenced_db = thd_make_lex_string( + thd, 0, name_buff, len, 1); + + /* Referenced (parent) table name */ + ptr = dict_remove_db_name(foreign->referenced_table_name); + len = filename_to_tablename(ptr, name_buff, sizeof(name_buff), 1); + f_key_info.referenced_table = thd_make_lex_string( + thd, 0, name_buff, len, 1); + + /* Dependent (child) database name */ + len = dict_get_db_name_len(foreign->foreign_table_name); + ut_a(len < sizeof(tmp_buff)); + memcpy(tmp_buff, foreign->foreign_table_name, len); + tmp_buff[len] = 0; + + len = filename_to_tablename(tmp_buff, name_buff, sizeof(name_buff)); + f_key_info.foreign_db = thd_make_lex_string( + thd, 0, name_buff, len, 1); + + /* Dependent (child) table name */ + ptr = dict_remove_db_name(foreign->foreign_table_name); + len = filename_to_tablename(ptr, name_buff, sizeof(name_buff), 1); + f_key_info.foreign_table = thd_make_lex_string( + thd, 0, name_buff, len, 1); + + do { + ptr = foreign->foreign_col_names[i]; + name = thd_make_lex_string(thd, name, ptr, + strlen(ptr), 1); + f_key_info.foreign_fields.push_back(name); + ptr = foreign->referenced_col_names[i]; + name = thd_make_lex_string(thd, name, ptr, + strlen(ptr), 1); + f_key_info.referenced_fields.push_back(name); + } while (++i < foreign->n_fields); + + if (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE) { + f_key_info.delete_method = FK_OPTION_CASCADE; + } else if (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) { + f_key_info.delete_method = FK_OPTION_SET_NULL; + } else if (foreign->type & DICT_FOREIGN_ON_DELETE_NO_ACTION) { + f_key_info.delete_method = FK_OPTION_NO_ACTION; + } else { + f_key_info.delete_method = FK_OPTION_RESTRICT; + } + + + if (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE) { + f_key_info.update_method = FK_OPTION_CASCADE; + } else if (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL) { + f_key_info.update_method = FK_OPTION_SET_NULL; + } else if (foreign->type & DICT_FOREIGN_ON_UPDATE_NO_ACTION) { + f_key_info.update_method = FK_OPTION_NO_ACTION; + } else { + f_key_info.update_method = FK_OPTION_RESTRICT; + } + + /* Load referenced table to update FK referenced key name. */ + if (foreign->referenced_table == NULL) { + + dict_table_t* ref_table = dict_table_open_on_name( + foreign->referenced_table_name_lookup, + true, DICT_ERR_IGNORE_NONE); + + if (ref_table == NULL) { + + if (!thd_test_options( + thd, OPTION_NO_FOREIGN_KEY_CHECKS)) { + ib::info() + << "Foreign Key referenced table " + << foreign->referenced_table_name + << " not found for foreign table " + << foreign->foreign_table_name; + } + } else { + dict_table_close(ref_table, true); + } + } + + if (foreign->referenced_index + && foreign->referenced_index->name != NULL) { + referenced_key_name = thd_make_lex_string( + thd, + f_key_info.referenced_key_name, + foreign->referenced_index->name, + strlen(foreign->referenced_index->name), + 1); + } else { + referenced_key_name = NULL; + } + + f_key_info.referenced_key_name = referenced_key_name; + + pf_key_info = (FOREIGN_KEY_INFO*) thd_memdup(thd, &f_key_info, + sizeof(FOREIGN_KEY_INFO)); + + return(pf_key_info); +} + +/*******************************************************************//** +Gets the list of foreign keys in this table. +@return always 0, that is, always succeeds */ + +int +ha_innobase::get_foreign_key_list( +/*==============================*/ + THD* thd, /*!< in: user thread handle */ + List<FOREIGN_KEY_INFO>* f_key_list) /*!< out: foreign key list */ +{ + update_thd(ha_thd()); + + m_prebuilt->trx->op_info = "getting list of foreign keys"; + + dict_sys.lock(SRW_LOCK_CALL); + + for (dict_foreign_set::iterator it + = m_prebuilt->table->foreign_set.begin(); + it != m_prebuilt->table->foreign_set.end(); + ++it) { + + FOREIGN_KEY_INFO* pf_key_info; + dict_foreign_t* foreign = *it; + + pf_key_info = get_foreign_key_info(thd, foreign); + + if (pf_key_info != NULL) { + f_key_list->push_back(pf_key_info); + } + } + + dict_sys.unlock(); + + m_prebuilt->trx->op_info = ""; + + return(0); +} + +/*******************************************************************//** +Gets the set of foreign keys where this table is the referenced table. +@return always 0, that is, always succeeds */ + +int +ha_innobase::get_parent_foreign_key_list( +/*=====================================*/ + THD* thd, /*!< in: user thread handle */ + List<FOREIGN_KEY_INFO>* f_key_list) /*!< out: foreign key list */ +{ + update_thd(ha_thd()); + + m_prebuilt->trx->op_info = "getting list of referencing foreign keys"; + + dict_sys.freeze(SRW_LOCK_CALL); + + for (dict_foreign_set::iterator it + = m_prebuilt->table->referenced_set.begin(); + it != m_prebuilt->table->referenced_set.end(); + ++it) { + + FOREIGN_KEY_INFO* pf_key_info; + dict_foreign_t* foreign = *it; + + pf_key_info = get_foreign_key_info(thd, foreign); + + if (pf_key_info != NULL) { + f_key_list->push_back(pf_key_info); + } + } + + dict_sys.unfreeze(); + + m_prebuilt->trx->op_info = ""; + + return(0); +} + +/** Table list item structure is used to store only the table +and name. It is used by get_cascade_foreign_key_table_list to store +the intermediate result for fetching the table set. */ +struct table_list_item { + /** InnoDB table object */ + const dict_table_t* table; + /** Table name */ + const char* name; +}; + +/** @return whether ALTER TABLE may change the storage engine of the table */ +bool ha_innobase::can_switch_engines() +{ + DBUG_ENTER("ha_innobase::can_switch_engines"); + update_thd(); + DBUG_RETURN(m_prebuilt->table->foreign_set.empty() && + m_prebuilt->table->referenced_set.empty()); +} + +/*******************************************************************//** +Checks if a table is referenced by a foreign key. The MySQL manual states that +a REPLACE is either equivalent to an INSERT, or DELETE(s) + INSERT. Only a +delete is then allowed internally to resolve a duplicate key conflict in +REPLACE, not an update. +@return > 0 if referenced by a FOREIGN KEY */ + +uint ha_innobase::referenced_by_foreign_key() +{ + dict_sys.freeze(SRW_LOCK_CALL); + const bool empty= m_prebuilt->table->referenced_set.empty(); + dict_sys.unfreeze(); + return !empty; +} + +/*******************************************************************//** +Tells something additional to the handler about how to do things. +@return 0 or error number */ + +int +ha_innobase::extra( +/*===============*/ + enum ha_extra_function operation) + /*!< in: HA_EXTRA_FLUSH or some other flag */ +{ + /* Warning: since it is not sure that MariaDB calls external_lock() + before calling this function, m_prebuilt->trx can be obsolete! */ + trx_t* trx = check_trx_exists(ha_thd()); + + switch (operation) { + case HA_EXTRA_FLUSH: + if (m_prebuilt->blob_heap) { + row_mysql_prebuilt_free_blob_heap(m_prebuilt); + } + break; + case HA_EXTRA_RESET_STATE: + reset_template(); + trx->duplicates = 0; + stmt_boundary: + trx->bulk_insert_apply(); + trx->end_bulk_insert(*m_prebuilt->table); + trx->bulk_insert = false; + break; + case HA_EXTRA_NO_KEYREAD: + m_prebuilt->read_just_key = 0; + break; + case HA_EXTRA_KEYREAD: + m_prebuilt->read_just_key = 1; + break; + case HA_EXTRA_KEYREAD_PRESERVE_FIELDS: + m_prebuilt->keep_other_fields_on_keyread = 1; + break; + case HA_EXTRA_INSERT_WITH_UPDATE: + trx->duplicates |= TRX_DUP_IGNORE; + goto stmt_boundary; + case HA_EXTRA_NO_IGNORE_DUP_KEY: + trx->duplicates &= ~TRX_DUP_IGNORE; + if (trx->is_bulk_insert()) { + /* Allow a subsequent INSERT into an empty table + if !unique_checks && !foreign_key_checks. */ + if (dberr_t err = trx->bulk_insert_apply()) { + return err; + } + break; + } + goto stmt_boundary; + case HA_EXTRA_WRITE_CAN_REPLACE: + trx->duplicates |= TRX_DUP_REPLACE; + goto stmt_boundary; + case HA_EXTRA_WRITE_CANNOT_REPLACE: + trx->duplicates &= ~TRX_DUP_REPLACE; + if (trx->is_bulk_insert()) { + /* Allow a subsequent INSERT into an empty table + if !unique_checks && !foreign_key_checks. */ + break; + } + goto stmt_boundary; + case HA_EXTRA_BEGIN_ALTER_COPY: + m_prebuilt->table->skip_alter_undo = 1; + if (m_prebuilt->table->is_temporary() + || !m_prebuilt->table->versioned_by_id()) { + break; + } + ut_ad(trx == m_prebuilt->trx); + trx_start_if_not_started(trx, true); + trx->mod_tables.emplace( + const_cast<dict_table_t*>(m_prebuilt->table), 0) + .first->second.set_versioned(0); + break; + case HA_EXTRA_END_ALTER_COPY: + m_prebuilt->table->skip_alter_undo = 0; + if (!m_prebuilt->table->is_temporary()) { + log_buffer_flush_to_disk(); + } + break; + default:/* Do nothing */ + ; + } + + return(0); +} + +/** +MySQL calls this method at the end of each statement */ +int +ha_innobase::reset() +{ + if (m_prebuilt->blob_heap) { + row_mysql_prebuilt_free_blob_heap(m_prebuilt); + } + + reset_template(); + + m_ds_mrr.dsmrr_close(); + + /* TODO: This should really be reset in reset_template() but for now + it's safer to do it explicitly here. */ + + /* This is a statement level counter. */ + m_prebuilt->autoinc_last_value = 0; + + m_prebuilt->skip_locked = false; + return(0); +} + +/******************************************************************//** +MySQL calls this function at the start of each SQL statement inside LOCK +TABLES. Inside LOCK TABLES the ::external_lock method does not work to +mark SQL statement borders. Note also a special case: if a temporary table +is created inside LOCK TABLES, MySQL has not called external_lock() at all +on that table. +MySQL-5.0 also calls this before each statement in an execution of a stored +procedure. To make the execution more deterministic for binlogging, MySQL-5.0 +locks all tables involved in a stored procedure with full explicit table +locks (thd_in_lock_tables(thd) holds in store_lock()) before executing the +procedure. +@return 0 or error code */ + +int +ha_innobase::start_stmt( +/*====================*/ + THD* thd, /*!< in: handle to the user thread */ + thr_lock_type lock_type) +{ + trx_t* trx = m_prebuilt->trx; + + DBUG_ENTER("ha_innobase::start_stmt"); + + update_thd(thd); + + ut_ad(m_prebuilt->table != NULL); + + trx = m_prebuilt->trx; + + /* Reset the AUTOINC statement level counter for multi-row INSERTs. */ + trx->n_autoinc_rows = 0; + + const auto sql_command = thd_sql_command(thd); + + m_prebuilt->hint_need_to_fetch_extra_cols = 0; + reset_template(); + + switch (sql_command) { + case SQLCOM_INSERT: + case SQLCOM_INSERT_SELECT: + if (trx->is_bulk_insert()) { + /* Allow a subsequent INSERT into an empty table + if !unique_checks && !foreign_key_checks. */ + break; + } + /* fall through */ + default: + trx->end_bulk_insert(*m_prebuilt->table); + if (!trx->bulk_insert) { + break; + } + + /* Trigger could've initiated another stmt. + So apply all bulk operation and mark as + end bulk insert for all tables */ + trx->bulk_insert_apply(); + trx->end_bulk_insert(); + trx->bulk_insert = false; + trx->last_sql_stat_start.least_undo_no = trx->undo_no; + } + + m_prebuilt->sql_stat_start = TRUE; + + if (m_prebuilt->table->is_temporary() + && m_mysql_has_locked + && m_prebuilt->select_lock_type == LOCK_NONE) { + switch (sql_command) { + case SQLCOM_INSERT: + case SQLCOM_UPDATE: + case SQLCOM_DELETE: + case SQLCOM_REPLACE: + init_table_handle_for_HANDLER(); + m_prebuilt->select_lock_type = LOCK_X; + m_prebuilt->stored_select_lock_type = LOCK_X; + if (dberr_t error = row_lock_table(m_prebuilt)) { + DBUG_RETURN(convert_error_code_to_mysql( + error, 0, thd)); + } + break; + } + } + + if (!m_mysql_has_locked) { + /* This handle is for a temporary table created inside + this same LOCK TABLES; since MySQL does NOT call external_lock + in this case, we must use x-row locks inside InnoDB to be + prepared for an update of a row */ + + m_prebuilt->select_lock_type = LOCK_X; + + } else if (sql_command == SQLCOM_SELECT + && lock_type == TL_READ + && trx->isolation_level != TRX_ISO_SERIALIZABLE) { + + /* For other than temporary tables, we obtain + no lock for consistent read (plain SELECT). */ + + m_prebuilt->select_lock_type = LOCK_NONE; + } else { + /* Not a consistent read: restore the + select_lock_type value. The value of + stored_select_lock_type was decided in: + 1) ::store_lock(), + 2) ::external_lock(), + 3) ::init_table_handle_for_HANDLER(). */ + + ut_a(m_prebuilt->stored_select_lock_type != LOCK_NONE_UNSET); + + m_prebuilt->select_lock_type = + m_prebuilt->stored_select_lock_type; + } + + *trx->detailed_error = 0; + + innobase_register_trx(ht, thd, trx); + + if (!trx_is_started(trx)) { + trx->will_lock = true; + } + + DBUG_RETURN(0); +} + +/******************************************************************//** +Maps a MySQL trx isolation level code to the InnoDB isolation level code +@return InnoDB isolation level */ +static inline +uint +innobase_map_isolation_level( +/*=========================*/ + enum_tx_isolation iso) /*!< in: MySQL isolation level code */ +{ + if (UNIV_UNLIKELY(srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN) + || UNIV_UNLIKELY(srv_read_only_mode)) { + return TRX_ISO_READ_UNCOMMITTED; + } + switch (iso) { + case ISO_REPEATABLE_READ: return(TRX_ISO_REPEATABLE_READ); + case ISO_READ_COMMITTED: return(TRX_ISO_READ_COMMITTED); + case ISO_SERIALIZABLE: return(TRX_ISO_SERIALIZABLE); + case ISO_READ_UNCOMMITTED: return(TRX_ISO_READ_UNCOMMITTED); + } + + ut_error; + + return(0); +} + +/******************************************************************//** +As MySQL will execute an external lock for every new table it uses when it +starts to process an SQL statement (an exception is when MySQL calls +start_stmt for the handle) we can use this function to store the pointer to +the THD in the handle. We will also use this function to communicate +to InnoDB that a new SQL statement has started and that we must store a +savepoint to our transaction handle, so that we are able to roll back +the SQL statement in case of an error. +@return 0 */ + +int +ha_innobase::external_lock( +/*=======================*/ + THD* thd, /*!< in: handle to the user thread */ + int lock_type) /*!< in: lock type */ +{ + DBUG_ENTER("ha_innobase::external_lock"); + DBUG_PRINT("enter",("lock_type: %d", lock_type)); + + update_thd(thd); + trx_t* trx = m_prebuilt->trx; + ut_ad(m_prebuilt->table); + + /* Statement based binlogging does not work in isolation level + READ UNCOMMITTED and READ COMMITTED since the necessary + locks cannot be taken. In this case, we print an + informative error message and return with an error. + Note: decide_logging_format would give the same error message, + except it cannot give the extra details. */ + + if (lock_type == F_WRLCK + && !(table_flags() & HA_BINLOG_STMT_CAPABLE) + && thd_binlog_format(thd) == BINLOG_FORMAT_STMT + && thd_binlog_filter_ok(thd) + && thd_sqlcom_can_generate_row_events(thd)) { + bool skip = false; +#ifdef WITH_WSREP + skip = trx->is_wsrep() && !wsrep_thd_is_local(thd); +#endif /* WITH_WSREP */ + /* used by test case */ + DBUG_EXECUTE_IF("no_innodb_binlog_errors", skip = true;); + + if (!skip) { + my_error(ER_BINLOG_STMT_MODE_AND_ROW_ENGINE, MYF(0), + " InnoDB is limited to row-logging when" + " transaction isolation level is" + " READ COMMITTED or READ UNCOMMITTED."); + + DBUG_RETURN(HA_ERR_LOGGING_IMPOSSIBLE); + } + } + + const auto sql_command = thd_sql_command(thd); + + /* Check for UPDATEs in read-only mode. */ + if (srv_read_only_mode) { + switch (sql_command) { + case SQLCOM_CREATE_TABLE: + if (lock_type != F_WRLCK) { + break; + } + /* fall through */ + case SQLCOM_UPDATE: + case SQLCOM_INSERT: + case SQLCOM_REPLACE: + case SQLCOM_DROP_TABLE: + case SQLCOM_ALTER_TABLE: + case SQLCOM_OPTIMIZE: + case SQLCOM_CREATE_INDEX: + case SQLCOM_DROP_INDEX: + case SQLCOM_CREATE_SEQUENCE: + case SQLCOM_DROP_SEQUENCE: + case SQLCOM_DELETE: + ib_senderrf(thd, IB_LOG_LEVEL_WARN, + ER_READ_ONLY_MODE); + DBUG_RETURN(HA_ERR_TABLE_READONLY); + } + } + + m_prebuilt->sql_stat_start = TRUE; + m_prebuilt->hint_need_to_fetch_extra_cols = 0; + + reset_template(); + switch (sql_command) { + case SQLCOM_INSERT: + case SQLCOM_INSERT_SELECT: + if (trx->is_bulk_insert()) { + /* Allow a subsequent INSERT into an empty table + if !unique_checks && !foreign_key_checks. */ + break; + } + /* fall through */ + default: + trx->end_bulk_insert(*m_prebuilt->table); + if (!trx->bulk_insert) { + break; + } + trx->bulk_insert = false; + trx->last_sql_stat_start.least_undo_no = trx->undo_no; + } + + switch (m_prebuilt->table->quiesce) { + case QUIESCE_START: + /* Check for FLUSH TABLE t WITH READ LOCK; */ + if (!srv_read_only_mode + && sql_command == SQLCOM_FLUSH + && lock_type == F_RDLCK) { + + if (!m_prebuilt->table->space) { + ib_senderrf(trx->mysql_thd, IB_LOG_LEVEL_ERROR, + ER_TABLESPACE_DISCARDED, + table->s->table_name.str); + + DBUG_RETURN(HA_ERR_TABLESPACE_MISSING); + } + + row_quiesce_table_start(m_prebuilt->table, trx); + + /* Use the transaction instance to track UNLOCK + TABLES. It can be done via START TRANSACTION; too + implicitly. */ + + ++trx->flush_tables; + } + break; + + case QUIESCE_COMPLETE: + /* Check for UNLOCK TABLES; implicit or explicit + or trx interruption. */ + if (trx->flush_tables > 0 + && (lock_type == F_UNLCK || trx_is_interrupted(trx))) { + + row_quiesce_table_complete(m_prebuilt->table, trx); + + ut_a(trx->flush_tables > 0); + --trx->flush_tables; + } + + break; + + case QUIESCE_NONE: + break; + } + + if (lock_type == F_WRLCK) { + + /* If this is a SELECT, then it is in UPDATE TABLE ... + or SELECT ... FOR UPDATE */ + m_prebuilt->select_lock_type = LOCK_X; + m_prebuilt->stored_select_lock_type = LOCK_X; + } + + if (lock_type != F_UNLCK) { + /* MySQL is setting a new table lock */ + + *trx->detailed_error = 0; + + innobase_register_trx(ht, thd, trx); + + if (trx->isolation_level == TRX_ISO_SERIALIZABLE + && m_prebuilt->select_lock_type == LOCK_NONE + && thd_test_options( + thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + + /* To get serializable execution, we let InnoDB + conceptually add 'LOCK IN SHARE MODE' to all SELECTs + which otherwise would have been consistent reads. An + exception is consistent reads in the AUTOCOMMIT=1 mode: + we know that they are read-only transactions, and they + can be serialized also if performed as consistent + reads. */ + + m_prebuilt->select_lock_type = LOCK_S; + m_prebuilt->stored_select_lock_type = LOCK_S; + } + + /* Starting from 4.1.9, no InnoDB table lock is taken in LOCK + TABLES if AUTOCOMMIT=1. It does not make much sense to acquire + an InnoDB table lock if it is released immediately at the end + of LOCK TABLES, and InnoDB's table locks in that case cause + VERY easily deadlocks. + + We do not set InnoDB table locks if user has not explicitly + requested a table lock. Note that thd_in_lock_tables(thd) + can hold in some cases, e.g., at the start of a stored + procedure call (SQLCOM_CALL). */ + + if (m_prebuilt->select_lock_type != LOCK_NONE) { + + if (sql_command == SQLCOM_LOCK_TABLES + && THDVAR(thd, table_locks) + && thd_test_options(thd, OPTION_NOT_AUTOCOMMIT) + && thd_in_lock_tables(thd)) { + + dberr_t error = row_lock_table(m_prebuilt); + + if (error != DB_SUCCESS) { + + DBUG_RETURN( + convert_error_code_to_mysql( + error, 0, thd)); + } + } + + trx->mysql_n_tables_locked++; + } + + trx->n_mysql_tables_in_use++; + m_mysql_has_locked = true; + + if (!trx_is_started(trx) + && (m_prebuilt->select_lock_type != LOCK_NONE + || m_prebuilt->stored_select_lock_type != LOCK_NONE)) { + + trx->will_lock = true; + } + + DBUG_RETURN(0); + } else { + DEBUG_SYNC_C("ha_innobase_end_statement"); + } + + /* MySQL is releasing a table lock */ + + trx->n_mysql_tables_in_use--; + m_mysql_has_locked = false; + + /* If the MySQL lock count drops to zero we know that the current SQL + statement has ended */ + + if (trx->n_mysql_tables_in_use == 0) { + + trx->mysql_n_tables_locked = 0; + m_prebuilt->used_in_HANDLER = FALSE; + + if (!thd_test_options( + thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + + if (trx_is_started(trx)) { + + innobase_commit(ht, thd, TRUE); + } + + } else if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) { + trx->read_view.close(); + } + } + + if (!trx_is_started(trx) + && lock_type != F_UNLCK + && (m_prebuilt->select_lock_type != LOCK_NONE + || m_prebuilt->stored_select_lock_type != LOCK_NONE)) { + + trx->will_lock = true; + } + + DBUG_RETURN(0); +} + +/************************************************************************//** +Here we export InnoDB status variables to MySQL. */ +static +void +innodb_export_status() +/*==================*/ +{ + if (srv_was_started) { + srv_export_innodb_status(); + } +} + +/************************************************************************//** +Implements the SHOW ENGINE INNODB STATUS command. Sends the output of the +InnoDB Monitor to the client. +@return 0 on success */ +static +int +innodb_show_status( +/*===============*/ + handlerton* hton, /*!< in: the innodb handlerton */ + THD* thd, /*!< in: the MySQL query thread of the caller */ + stat_print_fn* stat_print) +{ + static const char truncated_msg[] = "... truncated...\n"; + const long MAX_STATUS_SIZE = 1048576; + ulint trx_list_start = ULINT_UNDEFINED; + ulint trx_list_end = ULINT_UNDEFINED; + bool ret_val; + + DBUG_ENTER("innodb_show_status"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + /* We don't create the temp files or associated + mutexes in read-only-mode */ + + if (srv_read_only_mode) { + DBUG_RETURN(0); + } + + purge_sys.wake_if_not_active(); + + /* We let the InnoDB Monitor to output at most MAX_STATUS_SIZE + bytes of text. */ + + char* str; + size_t flen; + + mysql_mutex_lock(&srv_monitor_file_mutex); + rewind(srv_monitor_file); + + srv_printf_innodb_monitor(srv_monitor_file, FALSE, + &trx_list_start, &trx_list_end); + + os_file_set_eof(srv_monitor_file); + + flen = size_t(ftell(srv_monitor_file)); + if (ssize_t(flen) < 0) { + flen = 0; + } + + size_t usable_len; + + if (flen > MAX_STATUS_SIZE) { + usable_len = MAX_STATUS_SIZE; + truncated_status_writes++; + } else { + usable_len = flen; + } + + /* allocate buffer for the string, and + read the contents of the temporary file */ + + if (!(str = (char*) my_malloc(PSI_INSTRUMENT_ME, + usable_len + 1, MYF(0)))) { + mysql_mutex_unlock(&srv_monitor_file_mutex); + DBUG_RETURN(1); + } + + rewind(srv_monitor_file); + + if (flen < MAX_STATUS_SIZE) { + /* Display the entire output. */ + flen = fread(str, 1, flen, srv_monitor_file); + } else if (trx_list_end < flen + && trx_list_start < trx_list_end + && trx_list_start + flen - trx_list_end + < MAX_STATUS_SIZE - sizeof truncated_msg - 1) { + + /* Omit the beginning of the list of active transactions. */ + size_t len = fread(str, 1, trx_list_start, srv_monitor_file); + + memcpy(str + len, truncated_msg, sizeof truncated_msg - 1); + len += sizeof truncated_msg - 1; + usable_len = (MAX_STATUS_SIZE - 1) - len; + fseek(srv_monitor_file, long(flen - usable_len), SEEK_SET); + len += fread(str + len, 1, usable_len, srv_monitor_file); + flen = len; + } else { + /* Omit the end of the output. */ + flen = fread(str, 1, MAX_STATUS_SIZE - 1, srv_monitor_file); + } + + mysql_mutex_unlock(&srv_monitor_file_mutex); + + ret_val= stat_print( + thd, innobase_hton_name, + static_cast<uint>(strlen(innobase_hton_name)), + STRING_WITH_LEN(""), str, static_cast<uint>(flen)); + + my_free(str); + + DBUG_RETURN(ret_val); +} + +/************************************************************************//** +Return 0 on success and non-zero on failure. Note: the bool return type +seems to be abused here, should be an int. */ +static +bool +innobase_show_status( +/*=================*/ + handlerton* hton, /*!< in: the innodb handlerton */ + THD* thd, /*!< in: the MySQL query thread + of the caller */ + stat_print_fn* stat_print, + enum ha_stat_type stat_type) +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + switch (stat_type) { + case HA_ENGINE_STATUS: + /* Non-zero return value means there was an error. */ + return(innodb_show_status(hton, thd, stat_print) != 0); + + case HA_ENGINE_MUTEX: + case HA_ENGINE_LOGS: + /* Not handled */ + break; + } + + /* Success */ + return(false); +} + +/*********************************************************************//** +Returns number of THR_LOCK locks used for one instance of InnoDB table. +InnoDB no longer relies on THR_LOCK locks so 0 value is returned. +Instead of THR_LOCK locks InnoDB relies on combination of metadata locks +(e.g. for LOCK TABLES and DDL) and its own locking subsystem. +Note that even though this method returns 0, SQL-layer still calls +::store_lock(), ::start_stmt() and ::external_lock() methods for InnoDB +tables. */ + +uint +ha_innobase::lock_count(void) const +/*===============================*/ +{ + return 0; +} + +/*****************************************************************//** +Supposed to convert a MySQL table lock stored in the 'lock' field of the +handle to a proper type before storing pointer to the lock into an array +of pointers. +In practice, since InnoDB no longer relies on THR_LOCK locks and its +lock_count() method returns 0 it just informs storage engine about type +of THR_LOCK which SQL-layer would have acquired for this specific statement +on this specific table. +MySQL also calls this if it wants to reset some table locks to a not-locked +state during the processing of an SQL query. An example is that during a +SELECT the read lock is released early on the 'const' tables where we only +fetch one row. MySQL does not call this when it releases all locks at the +end of an SQL statement. +@return pointer to the current element in the 'to' array. */ + +THR_LOCK_DATA** +ha_innobase::store_lock( +/*====================*/ + THD* thd, /*!< in: user thread handle */ + THR_LOCK_DATA** to, /*!< in: pointer to the current + element in an array of pointers + to lock structs; + only used as return value */ + thr_lock_type lock_type) /*!< in: lock type to store in + 'lock'; this may also be + TL_IGNORE */ +{ + /* Note that trx in this function is NOT necessarily m_prebuilt->trx + because we call update_thd() later, in ::external_lock()! Failure to + understand this caused a serious memory corruption bug in 5.1.11. */ + + trx_t* trx = check_trx_exists(thd); + + /* NOTE: MySQL can call this function with lock 'type' TL_IGNORE! + Be careful to ignore TL_IGNORE if we are going to do something with + only 'real' locks! */ + + /* If no MySQL table is in use, we need to set the isolation level + of the transaction. */ + + if (lock_type != TL_IGNORE + && trx->n_mysql_tables_in_use == 0) { + trx->isolation_level = innobase_map_isolation_level( + (enum_tx_isolation) thd_tx_isolation(thd)); + + if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) { + + /* At low transaction isolation levels we let + each consistent read set its own snapshot */ + trx->read_view.close(); + } + } + + DBUG_ASSERT(EQ_CURRENT_THD(thd)); + const bool in_lock_tables = thd_in_lock_tables(thd); + const int sql_command = thd_sql_command(thd); + + if (srv_read_only_mode + && (sql_command == SQLCOM_UPDATE + || sql_command == SQLCOM_INSERT + || sql_command == SQLCOM_REPLACE + || sql_command == SQLCOM_DROP_TABLE + || sql_command == SQLCOM_ALTER_TABLE + || sql_command == SQLCOM_OPTIMIZE + || (sql_command == SQLCOM_CREATE_TABLE + && (lock_type >= TL_WRITE_CONCURRENT_INSERT + && lock_type <= TL_WRITE)) + || sql_command == SQLCOM_CREATE_INDEX + || sql_command == SQLCOM_DROP_INDEX + || sql_command == SQLCOM_CREATE_SEQUENCE + || sql_command == SQLCOM_DROP_SEQUENCE + || sql_command == SQLCOM_DELETE)) { + + ib_senderrf(trx->mysql_thd, + IB_LOG_LEVEL_WARN, ER_READ_ONLY_MODE); + + } else if (sql_command == SQLCOM_FLUSH + && lock_type == TL_READ_NO_INSERT) { + + /* Check for FLUSH TABLES ... WITH READ LOCK */ + + /* Note: This call can fail, but there is no way to return + the error to the caller. We simply ignore it for now here + and push the error code to the caller where the error is + detected in the function. */ + + dberr_t err = row_quiesce_set_state( + m_prebuilt->table, QUIESCE_START, trx); + + ut_a(err == DB_SUCCESS || err == DB_UNSUPPORTED); + + if (trx->isolation_level == TRX_ISO_SERIALIZABLE) { + m_prebuilt->select_lock_type = LOCK_S; + m_prebuilt->stored_select_lock_type = LOCK_S; + } else { + m_prebuilt->select_lock_type = LOCK_NONE; + m_prebuilt->stored_select_lock_type = LOCK_NONE; + } + + /* Check for DROP TABLE */ + } else if (sql_command == SQLCOM_DROP_TABLE || + sql_command == SQLCOM_DROP_SEQUENCE) { + + /* MySQL calls this function in DROP TABLE though this table + handle may belong to another thd that is running a query. Let + us in that case skip any changes to the m_prebuilt struct. */ + + /* Check for LOCK TABLE t1,...,tn WITH SHARED LOCKS */ + } else if ((lock_type == TL_READ && in_lock_tables) + || (lock_type == TL_READ_HIGH_PRIORITY && in_lock_tables) + || lock_type == TL_READ_WITH_SHARED_LOCKS + || lock_type == TL_READ_SKIP_LOCKED + || lock_type == TL_READ_NO_INSERT + || (lock_type != TL_IGNORE + && sql_command != SQLCOM_SELECT)) { + + /* The OR cases above are in this order: + 1) MySQL is doing LOCK TABLES ... READ LOCAL, or we + are processing a stored procedure or function, or + 2) (we do not know when TL_READ_HIGH_PRIORITY is used), or + 3) this is a SELECT ... IN SHARE MODE, or + 4) this is a SELECT ... IN SHARE MODE SKIP LOCKED, or + 5) we are doing a complex SQL statement like + INSERT INTO ... SELECT ... and the logical logging (MySQL + binlog) requires the use of a locking read, or + MySQL is doing LOCK TABLES ... READ. + 6) we let InnoDB do locking reads for all SQL statements that + are not simple SELECTs; note that select_lock_type in this + case may get strengthened in ::external_lock() to LOCK_X. + Note that we MUST use a locking read in all data modifying + SQL statements, because otherwise the execution would not be + serializable, and also the results from the update could be + unexpected if an obsolete consistent read view would be + used. */ + + /* Use consistent read for checksum table */ + + if (sql_command == SQLCOM_CHECKSUM + || sql_command == SQLCOM_CREATE_SEQUENCE + || (sql_command == SQLCOM_ANALYZE && lock_type == TL_READ) + || (trx->isolation_level <= TRX_ISO_READ_COMMITTED + && (lock_type == TL_READ + || lock_type == TL_READ_NO_INSERT) + && (sql_command == SQLCOM_INSERT_SELECT + || sql_command == SQLCOM_REPLACE_SELECT + || sql_command == SQLCOM_UPDATE + || sql_command == SQLCOM_CREATE_SEQUENCE + || sql_command == SQLCOM_CREATE_TABLE))) { + + /* If the transaction isolation level is + READ UNCOMMITTED or READ COMMITTED and we are executing + INSERT INTO...SELECT or REPLACE INTO...SELECT + or UPDATE ... = (SELECT ...) or CREATE ... + SELECT... without FOR UPDATE or IN SHARE + MODE in select, then we use consistent read + for select. */ + + m_prebuilt->select_lock_type = LOCK_NONE; + m_prebuilt->stored_select_lock_type = LOCK_NONE; + } else { + m_prebuilt->select_lock_type = LOCK_S; + m_prebuilt->stored_select_lock_type = LOCK_S; + } + + } else if (lock_type != TL_IGNORE) { + + /* We set possible LOCK_X value in external_lock, not yet + here even if this would be SELECT ... FOR UPDATE */ + + m_prebuilt->select_lock_type = LOCK_NONE; + m_prebuilt->stored_select_lock_type = LOCK_NONE; + } + m_prebuilt->skip_locked= (lock_type == TL_WRITE_SKIP_LOCKED || + lock_type == TL_READ_SKIP_LOCKED); + + if (!trx_is_started(trx) + && (m_prebuilt->select_lock_type != LOCK_NONE + || m_prebuilt->stored_select_lock_type != LOCK_NONE)) { + + trx->will_lock = true; + } + + return(to); +} + +/*********************************************************************//** +Read the next autoinc value. Acquire the relevant locks before reading +the AUTOINC value. If SUCCESS then the table AUTOINC mutex will be locked +on return and all relevant locks acquired. +@return DB_SUCCESS or error code */ + +dberr_t +ha_innobase::innobase_get_autoinc( +/*==============================*/ + ulonglong* value) /*!< out: autoinc value */ +{ + *value = 0; + + m_prebuilt->autoinc_error = innobase_lock_autoinc(); + + if (m_prebuilt->autoinc_error == DB_SUCCESS) { + + /* Determine the first value of the interval */ + *value = dict_table_autoinc_read(m_prebuilt->table); + + /* It should have been initialized during open. */ + if (*value == 0) { + m_prebuilt->autoinc_error = DB_UNSUPPORTED; + m_prebuilt->table->autoinc_mutex.wr_unlock(); + } + } + + return(m_prebuilt->autoinc_error); +} + +/*******************************************************************//** +This function reads the global auto-inc counter. It doesn't use the +AUTOINC lock even if the lock mode is set to TRADITIONAL. +@return the autoinc value */ + +ulonglong +ha_innobase::innobase_peek_autoinc(void) +/*====================================*/ +{ + ulonglong auto_inc; + dict_table_t* innodb_table; + + ut_a(m_prebuilt != NULL); + ut_a(m_prebuilt->table != NULL); + + innodb_table = m_prebuilt->table; + + innodb_table->autoinc_mutex.wr_lock(); + + auto_inc = dict_table_autoinc_read(innodb_table); + + if (auto_inc == 0) { + ib::info() << "AUTOINC next value generation is disabled for" + " '" << innodb_table->name << "'"; + } + + innodb_table->autoinc_mutex.wr_unlock(); + + return(auto_inc); +} + +/*********************************************************************//** +Returns the value of the auto-inc counter in *first_value and ~0 on failure. */ + +void +ha_innobase::get_auto_increment( +/*============================*/ + ulonglong offset, /*!< in: table autoinc offset */ + ulonglong increment, /*!< in: table autoinc + increment */ + ulonglong nb_desired_values, /*!< in: number of values + reqd */ + ulonglong* first_value, /*!< out: the autoinc value */ + ulonglong* nb_reserved_values) /*!< out: count of reserved + values */ +{ + trx_t* trx; + dberr_t error; + ulonglong autoinc = 0; + mariadb_set_stats set_stats_temporary(handler_stats); + + /* Prepare m_prebuilt->trx in the table handle */ + update_thd(ha_thd()); + + error = innobase_get_autoinc(&autoinc); + + if (error != DB_SUCCESS) { + *first_value = (~(ulonglong) 0); + return; + } + + /* This is a hack, since nb_desired_values seems to be accurate only + for the first call to get_auto_increment() for multi-row INSERT and + meaningless for other statements e.g, LOAD etc. Subsequent calls to + this method for the same statement results in different values which + don't make sense. Therefore we store the value the first time we are + called and count down from that as rows are written (see write_row()). + */ + + trx = m_prebuilt->trx; + + /* Note: We can't rely on *first_value since some MySQL engines, + in particular the partition engine, don't initialize it to 0 when + invoking this method. So we are not sure if it's guaranteed to + be 0 or not. */ + + /* We need the upper limit of the col type to check for + whether we update the table autoinc counter or not. */ + ulonglong col_max_value = + table->next_number_field->get_max_int_value(); + + /** The following logic is needed to avoid duplicate key error + for autoincrement column. + + (1) InnoDB gives the current autoincrement value with respect + to increment and offset value. + + (2) Basically it does compute_next_insert_id() logic inside InnoDB + to avoid the current auto increment value changed by handler layer. + + (3) It is restricted only for insert operations. */ + + if (increment > 1 && increment <= ~autoinc && autoinc < col_max_value + && thd_sql_command(m_user_thd) != SQLCOM_ALTER_TABLE) { + + ulonglong prev_auto_inc = autoinc; + + autoinc = ((autoinc - 1) + increment - offset)/ increment; + + autoinc = autoinc * increment + offset; + + /* If autoinc exceeds the col_max_value then reset + to old autoinc value. Because in case of non-strict + sql mode, boundary value is not considered as error. */ + + if (autoinc >= col_max_value) { + autoinc = prev_auto_inc; + } + + ut_ad(autoinc > 0); + } + + /* Called for the first time ? */ + if (trx->n_autoinc_rows == 0) { + + trx->n_autoinc_rows = (ulint) nb_desired_values; + + /* It's possible for nb_desired_values to be 0: + e.g., INSERT INTO T1(C) SELECT C FROM T2; */ + if (nb_desired_values == 0) { + + trx->n_autoinc_rows = 1; + } + + set_if_bigger(*first_value, autoinc); + /* Not in the middle of a mult-row INSERT. */ + } else if (m_prebuilt->autoinc_last_value == 0) { + set_if_bigger(*first_value, autoinc); + } + + if (*first_value > col_max_value) { + /* Out of range number. Let handler::update_auto_increment() + take care of this */ + m_prebuilt->autoinc_last_value = 0; + m_prebuilt->table->autoinc_mutex.wr_unlock(); + *nb_reserved_values= 0; + return; + } + + *nb_reserved_values = trx->n_autoinc_rows; + + /* With old style AUTOINC locking we only update the table's + AUTOINC counter after attempting to insert the row. */ + if (innobase_autoinc_lock_mode != AUTOINC_OLD_STYLE_LOCKING) { + ulonglong current; + ulonglong next_value; + + current = *first_value; + + /* Compute the last value in the interval */ + next_value = innobase_next_autoinc( + current, *nb_reserved_values, increment, offset, + col_max_value); + + m_prebuilt->autoinc_last_value = next_value; + + if (m_prebuilt->autoinc_last_value < *first_value) { + *first_value = (~(ulonglong) 0); + } else { + /* Update the table autoinc variable */ + dict_table_autoinc_update_if_greater( + m_prebuilt->table, + m_prebuilt->autoinc_last_value); + } + } else { + /* This will force write_row() into attempting an update + of the table's AUTOINC counter. */ + m_prebuilt->autoinc_last_value = 0; + } + + /* The increment to be used to increase the AUTOINC value, we use + this in write_row() and update_row() to increase the autoinc counter + for columns that are filled by the user. We need the offset and + the increment. */ + m_prebuilt->autoinc_offset = offset; + m_prebuilt->autoinc_increment = increment; + + m_prebuilt->table->autoinc_mutex.wr_unlock(); +} + +/*******************************************************************//** +See comment in handler.cc */ + +bool +ha_innobase::get_error_message( +/*===========================*/ + int error, + String* buf) +{ + trx_t* trx = check_trx_exists(ha_thd()); + + if (error == HA_ERR_DECRYPTION_FAILED) { + const char *msg = "Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match."; + buf->copy(msg, (uint)strlen(msg), system_charset_info); + } else { + buf->copy(trx->detailed_error, (uint) strlen(trx->detailed_error), + system_charset_info); + } + + return(FALSE); +} + +/** Retrieves the names of the table and the key for which there was a +duplicate entry in the case of HA_ERR_FOREIGN_DUPLICATE_KEY. + +If any of the names is not available, then this method will return +false and will not change any of child_table_name or child_key_name. + +@param[out] child_table_name Table name +@param[in] child_table_name_len Table name buffer size +@param[out] child_key_name Key name +@param[in] child_key_name_len Key name buffer size + +@retval true table and key names were available and were written into the +corresponding out parameters. +@retval false table and key names were not available, the out parameters +were not touched. */ +bool +ha_innobase::get_foreign_dup_key( +/*=============================*/ + char* child_table_name, + uint child_table_name_len, + char* child_key_name, + uint child_key_name_len) +{ + const dict_index_t* err_index; + + ut_a(m_prebuilt->trx != NULL); + ut_a(m_prebuilt->trx->magic_n == TRX_MAGIC_N); + + err_index = trx_get_error_info(m_prebuilt->trx); + + if (err_index == NULL) { + return(false); + } + /* else */ + + /* copy table name (and convert from filename-safe encoding to + system_charset_info) */ + char* p = strchr(err_index->table->name.m_name, '/'); + + /* strip ".../" prefix if any */ + if (p != NULL) { + p++; + } else { + p = err_index->table->name.m_name; + } + + size_t len; + + len = filename_to_tablename(p, child_table_name, child_table_name_len); + + child_table_name[len] = '\0'; + + /* copy index name */ + snprintf(child_key_name, child_key_name_len, "%s", + err_index->name()); + + return(true); +} + +/*******************************************************************//** +Compares two 'refs'. A 'ref' is the (internal) primary key value of the row. +If there is no explicitly declared non-null unique key or a primary key, then +InnoDB internally uses the row id as the primary key. +@return < 0 if ref1 < ref2, 0 if equal, else > 0 */ + +int +ha_innobase::cmp_ref( +/*=================*/ + const uchar* ref1, /*!< in: an (internal) primary key value in the + MySQL key value format */ + const uchar* ref2) /*!< in: an (internal) primary key value in the + MySQL key value format */ +{ + enum_field_types mysql_type; + Field* field; + KEY_PART_INFO* key_part; + KEY_PART_INFO* key_part_end; + uint len1; + uint len2; + int result; + + if (m_prebuilt->clust_index_was_generated) { + /* The 'ref' is an InnoDB row id */ + + return(memcmp(ref1, ref2, DATA_ROW_ID_LEN)); + } + + /* Do a type-aware comparison of primary key fields. PK fields + are always NOT NULL, so no checks for NULL are performed. */ + + key_part = table->key_info[table->s->primary_key].key_part; + + key_part_end = key_part + + table->key_info[table->s->primary_key].user_defined_key_parts; + + for (; key_part != key_part_end; ++key_part) { + field = key_part->field; + mysql_type = field->type(); + + if (mysql_type == MYSQL_TYPE_TINY_BLOB + || mysql_type == MYSQL_TYPE_MEDIUM_BLOB + || mysql_type == MYSQL_TYPE_BLOB + || mysql_type == MYSQL_TYPE_LONG_BLOB) { + + /* In the MySQL key value format, a column prefix of + a BLOB is preceded by a 2-byte length field */ + + len1 = innobase_read_from_2_little_endian(ref1); + len2 = innobase_read_from_2_little_endian(ref2); + + result = ((Field_blob*) field)->cmp( + ref1 + 2, len1, ref2 + 2, len2); + } else { + result = field->key_cmp(ref1, ref2); + } + + if (result) { + if (key_part->key_part_flag & HA_REVERSE_SORT) + result = -result; + return(result); + } + + ref1 += key_part->store_length; + ref2 += key_part->store_length; + } + + return(0); +} + +/*******************************************************************//** +Ask InnoDB if a query to a table can be cached. +@return TRUE if query caching of the table is permitted */ + +my_bool +ha_innobase::register_query_cache_table( +/*====================================*/ + THD* thd, /*!< in: user thread handle */ + const char* table_key, /*!< in: normalized path to the + table */ + uint key_length, /*!< in: length of the normalized + path to the table */ + qc_engine_callback* + call_back, /*!< out: pointer to function for + checking if query caching + is permitted */ + ulonglong *engine_data) /*!< in/out: data to call_back */ +{ + *engine_data = 0; + *call_back = innobase_query_caching_of_table_permitted; + + return(innobase_query_caching_of_table_permitted( + thd, table_key, + static_cast<uint>(key_length), + engine_data)); +} + +/******************************************************************//** +This function is used to find the storage length in bytes of the first n +characters for prefix indexes using a multibyte character set. The function +finds charset information and returns length of prefix_len characters in the +index field in bytes. +@return number of bytes occupied by the first n characters */ +ulint +innobase_get_at_most_n_mbchars( +/*===========================*/ + ulint charset_id, /*!< in: character set id */ + ulint prefix_len, /*!< in: prefix length in bytes of the index + (this has to be divided by mbmaxlen to get the + number of CHARACTERS n in the prefix) */ + ulint data_len, /*!< in: length of the string in bytes */ + const char* str) /*!< in: character string */ +{ + ulint char_length; /*!< character length in bytes */ + ulint n_chars; /*!< number of characters in prefix */ + CHARSET_INFO* charset; /*!< charset used in the field */ + + charset = get_charset((uint) charset_id, MYF(MY_WME)); + + ut_ad(charset); + ut_ad(charset->mbmaxlen); + + /* Calculate how many characters at most the prefix index contains */ + + n_chars = prefix_len / charset->mbmaxlen; + + /* If the charset is multi-byte, then we must find the length of the + first at most n chars in the string. If the string contains less + characters than n, then we return the length to the end of the last + character. */ + + if (charset->mbmaxlen > 1) { + /* charpos() returns the byte length of the first n_chars + characters, or a value bigger than the length of str, if + there were not enough full characters in str. + + Why does the code below work: + Suppose that we are looking for n UTF-8 characters. + + 1) If the string is long enough, then the prefix contains at + least n complete UTF-8 characters + maybe some extra + characters + an incomplete UTF-8 character. No problem in + this case. The function returns the pointer to the + end of the nth character. + + 2) If the string is not long enough, then the string contains + the complete value of a column, that is, only complete UTF-8 + characters, and we can store in the column prefix index the + whole string. */ + + char_length= charset->charpos(str, str + data_len, n_chars); + if (char_length > data_len) { + char_length = data_len; + } + } else if (data_len < prefix_len) { + + char_length = data_len; + + } else { + + char_length = prefix_len; + } + + return(char_length); +} + +/*******************************************************************//** +This function is used to prepare an X/Open XA distributed transaction. +@return 0 or error number */ +static +int +innobase_xa_prepare( +/*================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + THD* thd, /*!< in: handle to the MySQL thread of + the user whose XA transaction should + be prepared */ + bool prepare_trx) /*!< in: true - prepare transaction + false - the current SQL statement + ended */ +{ + trx_t* trx = check_trx_exists(thd); + + DBUG_ASSERT(hton == innodb_hton_ptr); + + thd_get_xid(thd, &reinterpret_cast<MYSQL_XID&>(trx->xid)); + + if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { + + sql_print_error("Transaction not registered for MariaDB 2PC," + " but transaction is active"); + } + + if (prepare_trx + || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { + + /* We were instructed to prepare the whole transaction, or + this is an SQL statement end and autocommit is on */ + + ut_ad(trx_is_registered_for_2pc(trx)); + + trx_prepare_for_mysql(trx); + } else { + /* We just mark the SQL statement ended and do not do a + transaction prepare */ + + /* If we had reserved the auto-inc lock for some + table in this SQL statement we release it now */ + + lock_unlock_table_autoinc(trx); + + /* Store the current undo_no of the transaction so that we + know where to roll back if we have to roll back the next + SQL statement */ + if (UNIV_UNLIKELY(end_of_statement(trx))) { + return 1; + } + } + + if (thd_sql_command(thd) != SQLCOM_XA_PREPARE + && (prepare_trx + || !thd_test_options( + thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { + + /* For mysqlbackup to work the order of transactions in binlog + and InnoDB must be the same. Consider the situation + + thread1> prepare; write to binlog; ... + <context switch> + thread2> prepare; write to binlog; commit + thread1> ... commit + + The server guarantees that writes to the binary log + and commits are in the same order, so we do not have + to handle this case. */ + } + + return(0); +} + +/*******************************************************************//** +This function is used to recover X/Open XA distributed transactions. +@return number of prepared transactions stored in xid_list */ +static +int +innobase_xa_recover( +/*================*/ + handlerton* hton, /*!< in: InnoDB handlerton */ + XID* xid_list,/*!< in/out: prepared transactions */ + uint len) /*!< in: number of slots in xid_list */ +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + if (len == 0 || xid_list == NULL) { + + return(0); + } + + return(trx_recover_for_mysql(xid_list, len)); +} + +/*******************************************************************//** +This function is used to commit one X/Open XA distributed transaction +which is in the prepared state +@return 0 or error number */ +static +int +innobase_commit_by_xid( +/*===================*/ + handlerton* hton, + XID* xid) /*!< in: X/Open XA transaction identification */ +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + DBUG_EXECUTE_IF("innobase_xa_fail", + return XAER_RMFAIL;); + + if (high_level_read_only) { + return(XAER_RMFAIL); + } + + if (trx_t* trx = trx_get_trx_by_xid(xid)) { + /* use cases are: disconnected xa, slave xa, recovery */ + innobase_commit_low(trx); + ut_ad(trx->mysql_thd == NULL); + trx_deregister_from_2pc(trx); + ut_ad(!trx->will_lock); /* trx cache requirement */ + trx->free(); + + return(XA_OK); + } else { + return(XAER_NOTA); + } +} + +/** This function is used to rollback one X/Open XA distributed transaction +which is in the prepared state + +@param[in] hton InnoDB handlerton +@param[in] xid X/Open XA transaction identification + +@return 0 or error number */ +int innobase_rollback_by_xid(handlerton* hton, XID* xid) +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + DBUG_EXECUTE_IF("innobase_xa_fail", + return XAER_RMFAIL;); + + if (high_level_read_only) { + return(XAER_RMFAIL); + } + + if (trx_t* trx = trx_get_trx_by_xid(xid)) { +#ifdef WITH_WSREP + /* If a wsrep transaction is being rolled back during + the recovery, we must clear the xid in order to avoid + writing serialisation history for rolled back transaction. */ + if (wsrep_is_wsrep_xid(&trx->xid)) { + trx->xid.null(); + } +#endif /* WITH_WSREP */ + int ret = innobase_rollback_trx(trx); + ut_ad(!trx->will_lock); + trx->free(); + + return(ret); + } else { + return(XAER_NOTA); + } +} + +bool +ha_innobase::check_if_incompatible_data( +/*====================================*/ + HA_CREATE_INFO* info, + uint table_changes) +{ + ha_table_option_struct *param_old, *param_new; + + /* Cache engine specific options */ + param_new = info->option_struct; + param_old = table->s->option_struct; + + innobase_copy_frm_flags_from_create_info(m_prebuilt->table, info); + + if (table_changes != IS_EQUAL_YES) { + + return(COMPATIBLE_DATA_NO); + } + + /* Check that auto_increment value was not changed */ + if ((info->used_fields & HA_CREATE_USED_AUTO) + && info->auto_increment_value != 0) { + + return(COMPATIBLE_DATA_NO); + } + + /* Check that row format didn't change */ + if ((info->used_fields & HA_CREATE_USED_ROW_FORMAT) + && info->row_type != get_row_type()) { + + return(COMPATIBLE_DATA_NO); + } + + /* Specifying KEY_BLOCK_SIZE requests a rebuild of the table. */ + if (info->used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE) { + return(COMPATIBLE_DATA_NO); + } + + /* Changes on engine specific table options requests a rebuild of the table. */ + if (param_new->page_compressed != param_old->page_compressed || + param_new->page_compression_level != param_old->page_compression_level) + { + return(COMPATIBLE_DATA_NO); + } + + return(COMPATIBLE_DATA_YES); +} + +/****************************************************************//** +Update the system variable innodb_io_capacity_max using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_io_capacity_max_update( +/*===========================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, void*, + const void* save) /*!< in: immediate result + from check function */ +{ + ulong in_val = *static_cast<const ulong*>(save); + + if (in_val < srv_io_capacity) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Setting innodb_io_capacity_max %lu" + " lower than innodb_io_capacity %lu.", + in_val, srv_io_capacity); + + srv_io_capacity = in_val; + + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Setting innodb_io_capacity to %lu", + srv_io_capacity); + } + + srv_max_io_capacity = in_val; +} + +/****************************************************************//** +Update the system variable innodb_io_capacity using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_io_capacity_update( +/*======================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, void*, + const void* save) /*!< in: immediate result + from check function */ +{ + ulong in_val = *static_cast<const ulong*>(save); + + if (in_val > srv_max_io_capacity) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Setting innodb_io_capacity to %lu" + " higher than innodb_io_capacity_max %lu", + in_val, srv_max_io_capacity); + + srv_max_io_capacity = (in_val & ~(~0UL >> 1)) + ? in_val : in_val * 2; + + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Setting innodb_max_io_capacity to %lu", + srv_max_io_capacity); + } + + srv_io_capacity = in_val; +} + +/****************************************************************//** +Update the system variable innodb_max_dirty_pages_pct using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_max_dirty_pages_pct_update( +/*==============================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, void*, + const void* save) /*!< in: immediate result + from check function */ +{ + double in_val = *static_cast<const double*>(save); + if (in_val < srv_max_dirty_pages_pct_lwm) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "innodb_max_dirty_pages_pct cannot be" + " set lower than" + " innodb_max_dirty_pages_pct_lwm."); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Lowering" + " innodb_max_dirty_page_pct_lwm to %lf", + in_val); + + srv_max_dirty_pages_pct_lwm = in_val; + } + + srv_max_buf_pool_modified_pct = in_val; + + mysql_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_lock(&buf_pool.flush_list_mutex); + buf_pool.page_cleaner_wakeup(); + mysql_mutex_unlock(&buf_pool.flush_list_mutex); + mysql_mutex_lock(&LOCK_global_system_variables); +} + +/****************************************************************//** +Update the system variable innodb_max_dirty_pages_pct_lwm using the +"saved" value. This function is registered as a callback with MySQL. */ +static +void +innodb_max_dirty_pages_pct_lwm_update( +/*==================================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, void*, + const void* save) /*!< in: immediate result + from check function */ +{ + double in_val = *static_cast<const double*>(save); + if (in_val > srv_max_buf_pool_modified_pct) { + in_val = srv_max_buf_pool_modified_pct; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "innodb_max_dirty_pages_pct_lwm" + " cannot be set higher than" + " innodb_max_dirty_pages_pct."); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Setting innodb_max_dirty_page_pct_lwm" + " to %lf", + in_val); + } + + srv_max_dirty_pages_pct_lwm = in_val; + + mysql_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_lock(&buf_pool.flush_list_mutex); + buf_pool.page_cleaner_wakeup(); + mysql_mutex_unlock(&buf_pool.flush_list_mutex); + mysql_mutex_lock(&LOCK_global_system_variables); +} + +/*************************************************************//** +Don't allow to set innodb_fast_shutdown=0 if purge threads are +already down. +@return 0 if innodb_fast_shutdown can be set */ +static +int +fast_shutdown_validate( +/*=============================*/ + THD* thd, /*!< in: thread handle */ + struct st_mysql_sys_var* var, /*!< in: pointer to system + variable */ + void* save, /*!< out: immediate result + for update function */ + struct st_mysql_value* value) /*!< in: incoming string */ +{ + if (check_sysvar_int(thd, var, save, value)) { + return(1); + } + + uint new_val = *reinterpret_cast<uint*>(save); + + if (srv_fast_shutdown && !new_val + && !srv_read_only_mode && abort_loop) { + return(1); + } + + return(0); +} + +/*************************************************************//** +Check whether valid argument given to innobase_*_stopword_table. +This function is registered as a callback with MySQL. +@return 0 for valid stopword table */ +static +int +innodb_stopword_table_validate( +/*===========================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, + void* save, /*!< out: immediate result + for update function */ + struct st_mysql_value* value) /*!< in: incoming string */ +{ + const char* stopword_table_name; + char buff[STRING_BUFFER_USUAL_SIZE]; + int len = sizeof(buff); + trx_t* trx; + + ut_a(save != NULL); + ut_a(value != NULL); + + stopword_table_name = value->val_str(value, buff, &len); + + trx = check_trx_exists(thd); + + row_mysql_lock_data_dictionary(trx); + + /* Validate the stopword table's (if supplied) existence and + of the right format */ + int ret = stopword_table_name && !fts_valid_stopword_table( + stopword_table_name, NULL); + + row_mysql_unlock_data_dictionary(trx); + + if (!ret) { + if (stopword_table_name == buff) { + ut_ad(static_cast<size_t>(len) < sizeof buff); + stopword_table_name = thd_strmake(thd, + stopword_table_name, + len); + } + + *static_cast<const char**>(save) = stopword_table_name; + } + + return(ret); +} + +extern void buf_resize_start(); + +/** Update the system variable innodb_buffer_pool_size using the "saved" +value. This function is registered as a callback with MySQL. +@param[in] save immediate result from check function */ +static +void +innodb_buffer_pool_size_update(THD*,st_mysql_sys_var*,void*, const void* save) +{ + snprintf(export_vars.innodb_buffer_pool_resize_status, + sizeof(export_vars.innodb_buffer_pool_resize_status), + "Buffer pool resize requested"); + + buf_resize_start(); +} + +/** The latest assigned innodb_ft_aux_table name */ +static char* innodb_ft_aux_table; + +/** Update innodb_ft_aux_table_id on SET GLOBAL innodb_ft_aux_table. +@param[in,out] thd connection +@param[out] save new value of innodb_ft_aux_table +@param[in] value user-specified value */ +static int innodb_ft_aux_table_validate(THD *thd, st_mysql_sys_var*, + void* save, st_mysql_value* value) +{ + char buf[STRING_BUFFER_USUAL_SIZE]; + int len = sizeof buf; + + if (const char* table_name = value->val_str(value, buf, &len)) { + if (dict_table_t* table = dict_table_open_on_name( + table_name, false, DICT_ERR_IGNORE_NONE)) { + const table_id_t id = dict_table_has_fts_index(table) + ? table->id : 0; + dict_table_close(table); + if (id) { + innodb_ft_aux_table_id = id; + if (table_name == buf) { + ut_ad(static_cast<size_t>(len) + < sizeof buf); + table_name = thd_strmake(thd, + table_name, + len); + } + + + *static_cast<const char**>(save) = table_name; + return 0; + } + } + + return 1; + } else { + *static_cast<char**>(save) = NULL; + innodb_ft_aux_table_id = 0; + return 0; + } +} + +#ifdef BTR_CUR_HASH_ADAPT +/****************************************************************//** +Update the system variable innodb_adaptive_hash_index using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_adaptive_hash_index_update(THD*, st_mysql_sys_var*, void*, + const void* save) +{ + mysql_mutex_unlock(&LOCK_global_system_variables); + if (*(my_bool*) save) { + btr_search_enable(); + } else { + btr_search_disable(); + } + mysql_mutex_lock(&LOCK_global_system_variables); +} +#endif /* BTR_CUR_HASH_ADAPT */ + +/****************************************************************//** +Update the system variable innodb_cmp_per_index using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_cmp_per_index_update(THD*, st_mysql_sys_var*, void*, const void* save) +{ + /* Reset the stats whenever we enable the table + INFORMATION_SCHEMA.innodb_cmp_per_index. */ + if (!srv_cmp_per_index_enabled && *(my_bool*) save) { + mysql_mutex_unlock(&LOCK_global_system_variables); + page_zip_reset_stat_per_index(); + mysql_mutex_lock(&LOCK_global_system_variables); + } + + srv_cmp_per_index_enabled = !!(*(my_bool*) save); +} + +/****************************************************************//** +Update the system variable innodb_old_blocks_pct using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_old_blocks_pct_update(THD*, st_mysql_sys_var*, void*, const void* save) +{ + mysql_mutex_unlock(&LOCK_global_system_variables); + uint ratio = buf_LRU_old_ratio_update(*static_cast<const uint*>(save), + true); + mysql_mutex_lock(&LOCK_global_system_variables); + innobase_old_blocks_pct = ratio; +} + +/****************************************************************//** +Update the system variable innodb_old_blocks_pct using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_change_buffer_max_size_update(THD*, st_mysql_sys_var*, void*, + const void* save) +{ + srv_change_buffer_max_size = *static_cast<const uint*>(save); + mysql_mutex_unlock(&LOCK_global_system_variables); + ibuf_max_size_update(srv_change_buffer_max_size); + mysql_mutex_lock(&LOCK_global_system_variables); +} + +#ifdef UNIV_DEBUG +static uint srv_fil_make_page_dirty_debug = 0; +static uint srv_saved_page_number_debug; + +/****************************************************************//** +Make the first page of given user tablespace dirty. */ +static +void +innodb_make_page_dirty(THD*, st_mysql_sys_var*, void*, const void* save) +{ + mtr_t mtr; + uint space_id = *static_cast<const uint*>(save); + mysql_mutex_unlock(&LOCK_global_system_variables); + fil_space_t* space = fil_space_t::get(space_id); + + if (space == NULL) { +func_exit_no_space: + mysql_mutex_lock(&LOCK_global_system_variables); + return; + } + + if (srv_saved_page_number_debug >= space->size) { +func_exit: + space->release(); + goto func_exit_no_space; + } + + mtr.start(); + mtr.set_named_space(space); + + buf_block_t* block = buf_page_get( + page_id_t(space_id, srv_saved_page_number_debug), + space->zip_size(), RW_X_LATCH, &mtr); + + if (block != NULL) { + ib::info() << "Dirtying page: " << block->page.id(); + mtr.write<1,mtr_t::FORCED>(*block, + block->page.frame + + FIL_PAGE_SPACE_ID, + block->page.frame + [FIL_PAGE_SPACE_ID]); + } + mtr.commit(); + log_write_up_to(mtr.commit_lsn(), true); + goto func_exit; +} +#endif // UNIV_DEBUG + +/****************************************************************//** +Update the monitor counter according to the "set_option", turn +on/off or reset specified monitor counter. */ +static +void +innodb_monitor_set_option( +/*======================*/ + const monitor_info_t* monitor_info,/*!< in: monitor info for the monitor + to set */ + mon_option_t set_option) /*!< in: Turn on/off reset the + counter */ +{ + monitor_id_t monitor_id = monitor_info->monitor_id; + + /* If module type is MONITOR_GROUP_MODULE, it cannot be + turned on/off individually. It should never use this + function to set options */ + ut_a(!(monitor_info->monitor_type & MONITOR_GROUP_MODULE)); + + switch (set_option) { + case MONITOR_TURN_ON: + MONITOR_ON(monitor_id); + MONITOR_INIT(monitor_id); + MONITOR_SET_START(monitor_id); + + /* If the monitor to be turned on uses + exisitng monitor counter (status variable), + make special processing to remember existing + counter value. */ + if (monitor_info->monitor_type & MONITOR_EXISTING) { + srv_mon_process_existing_counter( + monitor_id, MONITOR_TURN_ON); + } + break; + + case MONITOR_TURN_OFF: + if (monitor_info->monitor_type & MONITOR_EXISTING) { + srv_mon_process_existing_counter( + monitor_id, MONITOR_TURN_OFF); + } + + MONITOR_OFF(monitor_id); + MONITOR_SET_OFF(monitor_id); + break; + + case MONITOR_RESET_VALUE: + srv_mon_reset(monitor_id); + break; + + case MONITOR_RESET_ALL_VALUE: + srv_mon_reset_all(monitor_id); + break; + + default: + ut_error; + } +} + +/****************************************************************//** +Find matching InnoDB monitor counters and update their status +according to the "set_option", turn on/off or reset specified +monitor counter. */ +static +void +innodb_monitor_update_wildcard( +/*===========================*/ + const char* name, /*!< in: monitor name to match */ + mon_option_t set_option) /*!< in: the set option, whether + to turn on/off or reset the counter */ +{ + ut_a(name); + + for (ulint use = 0; use < NUM_MONITOR; use++) { + ulint type; + monitor_id_t monitor_id = static_cast<monitor_id_t>(use); + monitor_info_t* monitor_info; + + if (!innobase_wildcasecmp( + srv_mon_get_name(monitor_id), name)) { + monitor_info = srv_mon_get_info(monitor_id); + + type = monitor_info->monitor_type; + + /* If the monitor counter is of MONITOR_MODULE + type, skip it. Except for those also marked with + MONITOR_GROUP_MODULE flag, which can be turned + on only as a module. */ + if (!(type & MONITOR_MODULE) + && !(type & MONITOR_GROUP_MODULE)) { + innodb_monitor_set_option(monitor_info, + set_option); + } + + /* Need to special handle counters marked with + MONITOR_GROUP_MODULE, turn on the whole module if + any one of it comes here. Currently, only + "module_buf_page" is marked with MONITOR_GROUP_MODULE */ + if (type & MONITOR_GROUP_MODULE) { + if ((monitor_id >= MONITOR_MODULE_BUF_PAGE) + && (monitor_id < MONITOR_MODULE_OS)) { + if (set_option == MONITOR_TURN_ON + && MONITOR_IS_ON( + MONITOR_MODULE_BUF_PAGE)) { + continue; + } + + srv_mon_set_module_control( + MONITOR_MODULE_BUF_PAGE, + set_option); + } else { + /* If new monitor is added with + MONITOR_GROUP_MODULE, it needs + to be added here. */ + ut_ad(0); + } + } + } + } +} + +/*************************************************************//** +Given a configuration variable name, find corresponding monitor counter +and return its monitor ID if found. +@return monitor ID if found, MONITOR_NO_MATCH if there is no match */ +static +ulint +innodb_monitor_id_by_name_get( +/*==========================*/ + const char* name) /*!< in: monitor counter namer */ +{ + ut_a(name); + + /* Search for wild character '%' in the name, if + found, we treat it as a wildcard match. We do not search for + single character wildcard '_' since our monitor names already contain + such character. To avoid confusion, we request user must include + at least one '%' character to activate the wildcard search. */ + if (strchr(name, '%')) { + return(MONITOR_WILDCARD_MATCH); + } + + /* Not wildcard match, check for an exact match */ + for (ulint i = 0; i < NUM_MONITOR; i++) { + if (!innobase_strcasecmp( + name, srv_mon_get_name(static_cast<monitor_id_t>(i)))) { + return(i); + } + } + + return(MONITOR_NO_MATCH); +} +/*************************************************************//** +Validate that the passed in monitor name matches at least one +monitor counter name with wildcard compare. +@return TRUE if at least one monitor name matches */ +static +ibool +innodb_monitor_validate_wildcard_name( +/*==================================*/ + const char* name) /*!< in: monitor counter namer */ +{ + for (ulint i = 0; i < NUM_MONITOR; i++) { + if (!innobase_wildcasecmp( + srv_mon_get_name(static_cast<monitor_id_t>(i)), name)) { + return(TRUE); + } + } + + return(FALSE); +} +/*************************************************************//** +Validate the passed in monitor name, find and save the +corresponding monitor name in the function parameter "save". +@return 0 if monitor name is valid */ +static int innodb_monitor_valid_byname(const char *name) +{ + ulint use; + monitor_info_t* monitor_info; + + if (!name) { + return(1); + } + + use = innodb_monitor_id_by_name_get(name); + + /* No monitor name matches, nor it is wildcard match */ + if (use == MONITOR_NO_MATCH) { + return(1); + } + + if (use < NUM_MONITOR) { + monitor_info = srv_mon_get_info((monitor_id_t) use); + + /* If the monitor counter is marked with + MONITOR_GROUP_MODULE flag, then this counter + cannot be turned on/off individually, instead + it shall be turned on/off as a group using + its module name */ + if ((monitor_info->monitor_type & MONITOR_GROUP_MODULE) + && (!(monitor_info->monitor_type & MONITOR_MODULE))) { + sql_print_warning( + "Monitor counter '%s' cannot" + " be turned on/off individually." + " Please use its module name" + " to turn on/off the counters" + " in the module as a group.\n", + name); + + return(1); + } + + } else { + ut_a(use == MONITOR_WILDCARD_MATCH); + + /* For wildcard match, if there is not a single monitor + counter name that matches, treat it as an invalid + value for the system configuration variables */ + if (!innodb_monitor_validate_wildcard_name(name)) { + return(1); + } + } + + return(0); +} +/*************************************************************//** +Validate passed-in "value" is a valid monitor counter name. +This function is registered as a callback with MySQL. +@return 0 for valid name */ +static +int +innodb_monitor_validate( +/*====================*/ + THD*, st_mysql_sys_var*, + void* save, /*!< out: immediate result + for update function */ + struct st_mysql_value* value) /*!< in: incoming string */ +{ + int ret= 0; + + if (const char *name= value->val_str(value, nullptr, &ret)) + { + ret= innodb_monitor_valid_byname(name); + if (!ret) + *static_cast<const char**>(save)= name; + } + else + ret= 1; + + return ret; +} + +/****************************************************************//** +Update the system variable innodb_enable(disable/reset/reset_all)_monitor +according to the "set_option" and turn on/off or reset specified monitor +counter. */ +static +void +innodb_monitor_update( +/*==================*/ + THD* thd, /*!< in: thread handle */ + void* var_ptr, /*!< out: where the + formal string goes */ + const void* save, /*!< in: immediate result + from check function */ + mon_option_t set_option) /*!< in: the set option, + whether to turn on/off or + reset the counter */ +{ + monitor_info_t* monitor_info; + ulint monitor_id; + ulint err_monitor = 0; + const char* name; + + ut_a(save != NULL); + + name = *static_cast<const char*const*>(save); + + if (!name) { + monitor_id = MONITOR_DEFAULT_START; + } else { + monitor_id = innodb_monitor_id_by_name_get(name); + + /* Double check we have a valid monitor ID */ + if (monitor_id == MONITOR_NO_MATCH) { + return; + } + } + + if (monitor_id == MONITOR_DEFAULT_START) { + /* If user set the variable to "default", we will + print a message and make this set operation a "noop". + The check is being made here is because "set default" + does not go through validation function */ + if (thd) { + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_NO_DEFAULT, + "Default value is not defined for" + " this set option. Please specify" + " correct counter or module name."); + } else { + sql_print_error( + "Default value is not defined for" + " this set option. Please specify" + " correct counter or module name.\n"); + } + + if (var_ptr) { + *(const char**) var_ptr = NULL; + } + } else if (monitor_id == MONITOR_WILDCARD_MATCH) { + innodb_monitor_update_wildcard(name, set_option); + } else { + monitor_info = srv_mon_get_info( + static_cast<monitor_id_t>(monitor_id)); + + ut_a(monitor_info); + + /* If monitor is already truned on, someone could already + collect monitor data, exit and ask user to turn off the + monitor before turn it on again. */ + if (set_option == MONITOR_TURN_ON + && MONITOR_IS_ON(monitor_id)) { + err_monitor = monitor_id; + goto exit; + } + + if (var_ptr) { + *(const char**) var_ptr = monitor_info->monitor_name; + } + + /* Depending on the monitor name is for a module or + a counter, process counters in the whole module or + individual counter. */ + if (monitor_info->monitor_type & MONITOR_MODULE) { + srv_mon_set_module_control( + static_cast<monitor_id_t>(monitor_id), + set_option); + } else { + innodb_monitor_set_option(monitor_info, set_option); + } + } +exit: + /* Only if we are trying to turn on a monitor that already + been turned on, we will set err_monitor. Print related + information */ + if (err_monitor) { + sql_print_warning("InnoDB: Monitor %s is already enabled.", + srv_mon_get_name((monitor_id_t) err_monitor)); + } +} + +#ifdef UNIV_DEBUG +static char* srv_buffer_pool_evict; + +/****************************************************************//** +Evict all uncompressed pages of compressed tables from the buffer pool. +Keep the compressed pages in the buffer pool. +@return whether all uncompressed pages were evicted */ +static bool innodb_buffer_pool_evict_uncompressed() +{ + bool all_evicted = true; + + mysql_mutex_lock(&buf_pool.mutex); + + for (buf_block_t* block = UT_LIST_GET_LAST(buf_pool.unzip_LRU); + block != NULL; ) { + buf_block_t* prev_block = UT_LIST_GET_PREV(unzip_LRU, block); + ut_ad(block->page.in_file()); + ut_ad(block->page.belongs_to_unzip_LRU()); + ut_ad(block->in_unzip_LRU_list); + ut_ad(block->page.in_LRU_list); + + if (!buf_LRU_free_page(&block->page, false)) { + all_evicted = false; + block = prev_block; + } else { + /* Because buf_LRU_free_page() may release + and reacquire buf_pool.mutex, prev_block + may be invalid. */ + block = UT_LIST_GET_LAST(buf_pool.unzip_LRU); + } + } + + mysql_mutex_unlock(&buf_pool.mutex); + return(all_evicted); +} + +/****************************************************************//** +Called on SET GLOBAL innodb_buffer_pool_evict=... +Handles some values specially, to evict pages from the buffer pool. +SET GLOBAL innodb_buffer_pool_evict='uncompressed' +evicts all uncompressed page frames of compressed tablespaces. */ +static +void +innodb_buffer_pool_evict_update(THD*, st_mysql_sys_var*, void*, + const void* save) +{ + if (const char* op = *static_cast<const char*const*>(save)) { + if (!strcmp(op, "uncompressed")) { + mysql_mutex_unlock(&LOCK_global_system_variables); + for (uint tries = 0; tries < 10000; tries++) { + if (innodb_buffer_pool_evict_uncompressed()) { + mysql_mutex_lock( + &LOCK_global_system_variables); + return; + } + + std::this_thread::sleep_for( + std::chrono::milliseconds(10)); + } + + /* We failed to evict all uncompressed pages. */ + ut_ad(0); + } + } +} +#endif /* UNIV_DEBUG */ + +/****************************************************************//** +Update the system variable innodb_monitor_enable and enable +specified monitor counter. +This function is registered as a callback with MySQL. */ +static +void +innodb_enable_monitor_update( +/*=========================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, + void* var_ptr,/*!< out: where the + formal string goes */ + const void* save) /*!< in: immediate result + from check function */ +{ + innodb_monitor_update(thd, var_ptr, save, MONITOR_TURN_ON); +} + +/****************************************************************//** +Update the system variable innodb_monitor_disable and turn +off specified monitor counter. */ +static +void +innodb_disable_monitor_update( +/*==========================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, + void* var_ptr,/*!< out: where the + formal string goes */ + const void* save) /*!< in: immediate result + from check function */ +{ + innodb_monitor_update(thd, var_ptr, save, MONITOR_TURN_OFF); +} + +/****************************************************************//** +Update the system variable innodb_monitor_reset and reset +specified monitor counter(s). +This function is registered as a callback with MySQL. */ +static +void +innodb_reset_monitor_update( +/*========================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, + void* var_ptr,/*!< out: where the + formal string goes */ + const void* save) /*!< in: immediate result + from check function */ +{ + innodb_monitor_update(thd, var_ptr, save, MONITOR_RESET_VALUE); +} + +/****************************************************************//** +Update the system variable innodb_monitor_reset_all and reset +all value related monitor counter. +This function is registered as a callback with MySQL. */ +static +void +innodb_reset_all_monitor_update( +/*============================*/ + THD* thd, /*!< in: thread handle */ + st_mysql_sys_var*, + void* var_ptr,/*!< out: where the + formal string goes */ + const void* save) /*!< in: immediate result + from check function */ +{ + innodb_monitor_update(thd, var_ptr, save, MONITOR_RESET_ALL_VALUE); +} + +static +void +innodb_defragment_frequency_update(THD*, st_mysql_sys_var*, void*, + const void* save) +{ + srv_defragment_frequency = (*static_cast<const uint*>(save)); + srv_defragment_interval = 1000000000ULL / srv_defragment_frequency; +} + +static inline char *my_strtok_r(char *str, const char *delim, char **saveptr) +{ +#if defined _WIN32 + return strtok_s(str, delim, saveptr); +#else + return strtok_r(str, delim, saveptr); +#endif +} + +/****************************************************************//** +Parse and enable InnoDB monitor counters during server startup. +User can list the monitor counters/groups to be enable by specifying +"loose-innodb_monitor_enable=monitor_name1;monitor_name2..." +in server configuration file or at the command line. The string +separate could be ";", "," or empty space. */ +static +void +innodb_enable_monitor_at_startup( +/*=============================*/ + char* str) /*!< in/out: monitor counter enable list */ +{ + static const char* sep = " ;,"; + char* last; + + ut_a(str); + + /* Walk through the string, and separate each monitor counter + and/or counter group name, and calling innodb_monitor_update() + if successfully updated. Please note that the "str" would be + changed by strtok_r() as it walks through it. */ + for (char* option = my_strtok_r(str, sep, &last); + option; + option = my_strtok_r(NULL, sep, &last)) { + if (!innodb_monitor_valid_byname(option)) { + innodb_monitor_update(NULL, NULL, &option, + MONITOR_TURN_ON); + } else { + sql_print_warning("Invalid monitor counter" + " name: '%s'", option); + } + } +} + +/****************************************************************//** +Callback function for accessing the InnoDB variables from MySQL: +SHOW VARIABLES. */ +static int show_innodb_vars(THD*, SHOW_VAR* var, void *, + struct system_status_var *status_var, + enum enum_var_type var_type) +{ + innodb_export_status(); + var->type = SHOW_ARRAY; + var->value = (char*) &innodb_status_variables; + //var->scope = SHOW_SCOPE_GLOBAL; + + return(0); +} + +/****************************************************************//** +This function checks each index name for a table against reserved +system default primary index name 'GEN_CLUST_INDEX'. If a name +matches, this function pushes an warning message to the client, +and returns true. +@return true if the index name matches the reserved name */ +bool +innobase_index_name_is_reserved( +/*============================*/ + THD* thd, /*!< in/out: MySQL connection */ + const KEY* key_info, /*!< in: Indexes to be created */ + ulint num_of_keys) /*!< in: Number of indexes to + be created. */ +{ + const KEY* key; + uint key_num; /* index number */ + + for (key_num = 0; key_num < num_of_keys; key_num++) { + key = &key_info[key_num]; + + if (innobase_strcasecmp(key->name.str, + innobase_index_reserve_name) == 0) { + /* Push warning to mysql */ + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_NAME_FOR_INDEX, + "Cannot Create Index with name" + " '%s'. The name is reserved" + " for the system default primary" + " index.", + innobase_index_reserve_name); + + my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), + innobase_index_reserve_name); + + return(true); + } + } + + return(false); +} + +/** Retrieve the FTS Relevance Ranking result for doc with doc_id +of m_prebuilt->fts_doc_id +@param[in,out] fts_hdl FTS handler +@return the relevance ranking value */ +static +float +innobase_fts_retrieve_ranking( + FT_INFO* fts_hdl) +{ + fts_result_t* result; + row_prebuilt_t* ft_prebuilt; + + result = reinterpret_cast<NEW_FT_INFO*>(fts_hdl)->ft_result; + + ft_prebuilt = reinterpret_cast<NEW_FT_INFO*>(fts_hdl)->ft_prebuilt; + + fts_ranking_t* ranking = rbt_value(fts_ranking_t, result->current); + ft_prebuilt->fts_doc_id= ranking->doc_id; + + return(ranking->rank); +} + +/** Free the memory for the FTS handler +@param[in,out] fts_hdl FTS handler */ +static +void +innobase_fts_close_ranking( + FT_INFO* fts_hdl) +{ + fts_result_t* result; + + result = reinterpret_cast<NEW_FT_INFO*>(fts_hdl)->ft_result; + + fts_query_free_result(result); + + my_free((uchar*) fts_hdl); +} + +/** Find and Retrieve the FTS Relevance Ranking result for doc with doc_id +of m_prebuilt->fts_doc_id +@param[in,out] fts_hdl FTS handler +@return the relevance ranking value */ +static +float +innobase_fts_find_ranking(FT_INFO* fts_hdl, uchar*, uint) +{ + fts_result_t* result; + row_prebuilt_t* ft_prebuilt; + + ft_prebuilt = reinterpret_cast<NEW_FT_INFO*>(fts_hdl)->ft_prebuilt; + result = reinterpret_cast<NEW_FT_INFO*>(fts_hdl)->ft_result; + + /* Retrieve the ranking value for doc_id with value of + m_prebuilt->fts_doc_id */ + return(fts_retrieve_ranking(result, ft_prebuilt->fts_doc_id)); +} + +#ifdef UNIV_DEBUG +static my_bool innodb_log_checkpoint_now = TRUE; +static my_bool innodb_buf_flush_list_now = TRUE; +static uint innodb_merge_threshold_set_all_debug + = DICT_INDEX_MERGE_THRESHOLD_DEFAULT; + +/** Force an InnoDB log checkpoint. */ +static +void +checkpoint_now_set(THD*, st_mysql_sys_var*, void*, const void *save) +{ + if (!*static_cast<const my_bool*>(save)) + return; + const auto size= log_sys.is_encrypted() + ? SIZE_OF_FILE_CHECKPOINT + 8 : SIZE_OF_FILE_CHECKPOINT; + mysql_mutex_unlock(&LOCK_global_system_variables); + lsn_t lsn; + while (log_sys.last_checkpoint_lsn.load(std::memory_order_acquire) + size < + (lsn= log_sys.get_lsn(std::memory_order_acquire))) + log_make_checkpoint(); + + mysql_mutex_lock(&LOCK_global_system_variables); +} + +/****************************************************************//** +Force a dirty pages flush now. */ +static +void +buf_flush_list_now_set(THD*, st_mysql_sys_var*, void*, const void* save) +{ + if (*(my_bool*) save) { + mysql_mutex_unlock(&LOCK_global_system_variables); + buf_flush_sync(); + mysql_mutex_lock(&LOCK_global_system_variables); + } +} + +/** Override current MERGE_THRESHOLD setting for all indexes at dictionary +now. +@param[in] save immediate result from check function */ +static +void +innodb_merge_threshold_set_all_debug_update(THD*, st_mysql_sys_var*, void*, + const void* save) +{ + innodb_merge_threshold_set_all_debug + = (*static_cast<const uint*>(save)); + dict_set_merge_threshold_all_debug( + innodb_merge_threshold_set_all_debug); +} +#endif /* UNIV_DEBUG */ + +/** Find and Retrieve the FTS doc_id for the current result row +@param[in,out] fts_hdl FTS handler +@return the document ID */ +static +ulonglong +innobase_fts_retrieve_docid( + FT_INFO_EXT* fts_hdl) +{ + fts_result_t* result; + row_prebuilt_t* ft_prebuilt; + + ft_prebuilt = reinterpret_cast<NEW_FT_INFO *>(fts_hdl)->ft_prebuilt; + result = reinterpret_cast<NEW_FT_INFO *>(fts_hdl)->ft_result; + + if (ft_prebuilt->read_just_key) { + + fts_ranking_t* ranking = + rbt_value(fts_ranking_t, result->current); + + return(ranking->doc_id); + } + + return(ft_prebuilt->fts_doc_id); +} + +/* These variables are never read by InnoDB or changed. They are a kind of +dummies that are needed by the MySQL infrastructure to call +buffer_pool_dump_now(), buffer_pool_load_now() and buffer_pool_load_abort() +by the user by doing: + SET GLOBAL innodb_buffer_pool_dump_now=ON; + SET GLOBAL innodb_buffer_pool_load_now=ON; + SET GLOBAL innodb_buffer_pool_load_abort=ON; +Their values are read by MySQL and displayed to the user when the variables +are queried, e.g.: + SELECT @@innodb_buffer_pool_dump_now; + SELECT @@innodb_buffer_pool_load_now; + SELECT @@innodb_buffer_pool_load_abort; */ +static my_bool innodb_buffer_pool_dump_now = FALSE; +static my_bool innodb_buffer_pool_load_now = FALSE; +static my_bool innodb_buffer_pool_load_abort = FALSE; + +/****************************************************************//** +Trigger a dump of the buffer pool if innodb_buffer_pool_dump_now is set +to ON. This function is registered as a callback with MySQL. */ +static +void +buffer_pool_dump_now( +/*=================*/ + THD* thd /*!< in: thread handle */ + MY_ATTRIBUTE((unused)), + struct st_mysql_sys_var* var /*!< in: pointer to system + variable */ + MY_ATTRIBUTE((unused)), + void* var_ptr /*!< out: where the formal + string goes */ + MY_ATTRIBUTE((unused)), + const void* save) /*!< in: immediate result from + check function */ +{ + if (*(my_bool*) save && !srv_read_only_mode) { + mysql_mutex_unlock(&LOCK_global_system_variables); + buf_dump_start(); + mysql_mutex_lock(&LOCK_global_system_variables); + } +} + +/****************************************************************//** +Trigger a load of the buffer pool if innodb_buffer_pool_load_now is set +to ON. This function is registered as a callback with MySQL. */ +static +void +buffer_pool_load_now( +/*=================*/ + THD* thd /*!< in: thread handle */ + MY_ATTRIBUTE((unused)), + struct st_mysql_sys_var* var /*!< in: pointer to system + variable */ + MY_ATTRIBUTE((unused)), + void* var_ptr /*!< out: where the formal + string goes */ + MY_ATTRIBUTE((unused)), + const void* save) /*!< in: immediate result from + check function */ +{ + if (*(my_bool*) save && !srv_read_only_mode) { + mysql_mutex_unlock(&LOCK_global_system_variables); + buf_load_start(); + mysql_mutex_lock(&LOCK_global_system_variables); + } +} + +/****************************************************************//** +Abort a load of the buffer pool if innodb_buffer_pool_load_abort +is set to ON. This function is registered as a callback with MySQL. */ +static +void +buffer_pool_load_abort( +/*===================*/ + THD* thd /*!< in: thread handle */ + MY_ATTRIBUTE((unused)), + struct st_mysql_sys_var* var /*!< in: pointer to system + variable */ + MY_ATTRIBUTE((unused)), + void* var_ptr /*!< out: where the formal + string goes */ + MY_ATTRIBUTE((unused)), + const void* save) /*!< in: immediate result from + check function */ +{ + if (*(my_bool*) save && !srv_read_only_mode) { + mysql_mutex_unlock(&LOCK_global_system_variables); + buf_load_abort(); + mysql_mutex_lock(&LOCK_global_system_variables); + } +} + +#if defined __linux__ || defined _WIN32 +static void innodb_log_file_buffering_update(THD *thd, st_mysql_sys_var*, + void *, const void *save) +{ + mysql_mutex_unlock(&LOCK_global_system_variables); + log_sys.set_buffered(*static_cast<const my_bool*>(save)); + mysql_mutex_lock(&LOCK_global_system_variables); +} +#endif + +static void innodb_log_file_size_update(THD *thd, st_mysql_sys_var*, + void *var, const void *save) +{ + ut_ad(var == &srv_log_file_size); + mysql_mutex_unlock(&LOCK_global_system_variables); + + if (high_level_read_only) + ib_senderrf(thd, IB_LOG_LEVEL_ERROR, ER_READ_ONLY_MODE); + else if (!log_sys.is_pmem() && + *static_cast<const ulonglong*>(save) < log_sys.buf_size) + my_printf_error(ER_WRONG_ARGUMENTS, + "innodb_log_file_size must be at least" + " innodb_log_buffer_size=%zu", MYF(0), log_sys.buf_size); + else + { + switch (log_sys.resize_start(*static_cast<const ulonglong*>(save))) { + case log_t::RESIZE_NO_CHANGE: + break; + case log_t::RESIZE_IN_PROGRESS: + my_printf_error(ER_WRONG_USAGE, + "innodb_log_file_size change is already in progress", + MYF(0)); + break; + case log_t::RESIZE_FAILED: + ib_senderrf(thd, IB_LOG_LEVEL_ERROR, ER_CANT_CREATE_HANDLER_FILE); + break; + case log_t::RESIZE_STARTED: + for (timespec abstime;;) + { + if (thd_kill_level(thd)) + { + log_sys.resize_abort(); + break; + } + + set_timespec(abstime, 5); + mysql_mutex_lock(&buf_pool.flush_list_mutex); + const bool in_progress(buf_pool.get_oldest_modification(LSN_MAX) < + log_sys.resize_in_progress()); + if (in_progress) + my_cond_timedwait(&buf_pool.do_flush_list, + &buf_pool.flush_list_mutex.m_mutex, &abstime); + mysql_mutex_unlock(&buf_pool.flush_list_mutex); + if (!log_sys.resize_in_progress()) + break; + } + } + } + mysql_mutex_lock(&LOCK_global_system_variables); +} + +/** Update innodb_status_output or innodb_status_output_locks, +which control InnoDB "status monitor" output to the error log. +@param[out] var current value +@param[in] save to-be-assigned value */ +static +void +innodb_status_output_update(THD*,st_mysql_sys_var*,void*var,const void*save) +{ + if (srv_monitor_timer) + { + *static_cast<my_bool*>(var)= *static_cast<const my_bool*>(save); + mysql_mutex_unlock(&LOCK_global_system_variables); + /* Wakeup server monitor. */ + srv_monitor_timer_schedule_now(); + mysql_mutex_lock(&LOCK_global_system_variables); + } +} + +/** Update the system variable innodb_encryption_threads. +@param[in] save to-be-assigned value */ +static +void +innodb_encryption_threads_update(THD*,st_mysql_sys_var*,void*,const void*save) +{ + mysql_mutex_unlock(&LOCK_global_system_variables); + fil_crypt_set_thread_cnt(*static_cast<const uint*>(save)); + mysql_mutex_lock(&LOCK_global_system_variables); +} + +/** Update the system variable innodb_encryption_rotate_key_age. +@param[in] save to-be-assigned value */ +static +void +innodb_encryption_rotate_key_age_update(THD*, st_mysql_sys_var*, void*, + const void* save) +{ + mysql_mutex_unlock(&LOCK_global_system_variables); + fil_crypt_set_rotate_key_age(*static_cast<const uint*>(save)); + mysql_mutex_lock(&LOCK_global_system_variables); +} + +/** Update the system variable innodb_encryption_rotation_iops. +@param[in] save to-be-assigned value */ +static +void +innodb_encryption_rotation_iops_update(THD*, st_mysql_sys_var*, void*, + const void* save) +{ + mysql_mutex_unlock(&LOCK_global_system_variables); + fil_crypt_set_rotation_iops(*static_cast<const uint*>(save)); + mysql_mutex_lock(&LOCK_global_system_variables); +} + +/** Update the system variable innodb_encrypt_tables. +@param[in] save to-be-assigned value */ +static +void +innodb_encrypt_tables_update(THD*, st_mysql_sys_var*, void*, const void* save) +{ + mysql_mutex_unlock(&LOCK_global_system_variables); + fil_crypt_set_encrypt_tables(*static_cast<const ulong*>(save)); + mysql_mutex_lock(&LOCK_global_system_variables); +} + +static SHOW_VAR innodb_status_variables_export[]= { + SHOW_FUNC_ENTRY("Innodb", &show_innodb_vars), + {NullS, NullS, SHOW_LONG} +}; + +static struct st_mysql_storage_engine innobase_storage_engine= +{ MYSQL_HANDLERTON_INTERFACE_VERSION }; + +#ifdef WITH_WSREP +/** Request a transaction to be killed that holds a conflicting lock. +@param bf_trx brute force applier transaction +@param thd_id thd_get_thread_id(victim_trx->mysql_htd) +@param trx_id victim_trx->id */ +void lock_wait_wsrep_kill(trx_t *bf_trx, ulong thd_id, trx_id_t trx_id) +{ + THD *bf_thd= bf_trx->mysql_thd; + + if (THD *vthd= find_thread_by_id(thd_id)) + { + bool aborting= false; + wsrep_thd_LOCK(vthd); + trx_t *vtrx= thd_to_trx(vthd); + if (vtrx) + { + /* Do not bother with lock elision using transactional memory here; + this is rather complex code */ + LockMutexGuard g{SRW_LOCK_CALL}; + mysql_mutex_lock(&lock_sys.wait_mutex); + vtrx->mutex_lock(); + /* victim transaction is either active or prepared, if it has already + proceeded to replication phase */ + if (vtrx->id == trx_id) + { + switch (vtrx->state) { + default: + break; + case TRX_STATE_PREPARED: + if (!wsrep_is_wsrep_xid(&vtrx->xid)) + break; + /* fall through */ + case TRX_STATE_ACTIVE: + WSREP_LOG_CONFLICT(bf_thd, vthd, TRUE); + WSREP_DEBUG("Aborter BF trx_id: " TRX_ID_FMT " thread: %ld " + "seqno: %lld client_state: %s " + "client_mode: %s transaction_mode: %s query: %s", + bf_trx->id, + thd_get_thread_id(bf_thd), + wsrep_thd_trx_seqno(bf_thd), + wsrep_thd_client_state_str(bf_thd), + wsrep_thd_client_mode_str(bf_thd), + wsrep_thd_transaction_state_str(bf_thd), + wsrep_thd_query(bf_thd)); + WSREP_DEBUG("Victim %s trx_id: " TRX_ID_FMT " thread: %ld " + "seqno: %lld client_state: %s " + "client_mode: %s transaction_mode: %s query: %s", + wsrep_thd_is_BF(vthd, false) ? "BF" : "normal", + vtrx->id, + thd_get_thread_id(vthd), + wsrep_thd_trx_seqno(vthd), + wsrep_thd_client_state_str(vthd), + wsrep_thd_client_mode_str(vthd), + wsrep_thd_transaction_state_str(vthd), + wsrep_thd_query(vthd)); + aborting= true; + } + } + mysql_mutex_unlock(&lock_sys.wait_mutex); + vtrx->mutex_unlock(); + } + + DEBUG_SYNC(bf_thd, "before_wsrep_thd_abort"); + if (aborting && wsrep_thd_bf_abort(bf_thd, vthd, true)) + { + /* Need to grab mutexes again to ensure that the trx is still in + right state. */ + lock_sys.wr_lock(SRW_LOCK_CALL); + mysql_mutex_lock(&lock_sys.wait_mutex); + vtrx->mutex_lock(); + + /* if victim is waiting for some other lock, we have to cancel + that waiting + */ + if (vtrx->id == trx_id) + { + switch (vtrx->state) { + default: + break; + case TRX_STATE_ACTIVE: + case TRX_STATE_PREPARED: + lock_sys.cancel_lock_wait_for_wsrep_bf_abort(vtrx); + } + } + lock_sys.wr_unlock(); + mysql_mutex_unlock(&lock_sys.wait_mutex); + vtrx->mutex_unlock(); + } + else + { + WSREP_DEBUG("wsrep_thd_bf_abort has failed, victim %lu will survive", + thd_get_thread_id(vthd)); + } + wsrep_thd_UNLOCK(vthd); + wsrep_thd_kill_UNLOCK(vthd); + } +} + +/** This function forces the victim transaction to abort. Aborting the + transaction does NOT end it, it still has to be rolled back. + + The caller must lock LOCK_thd_kill and LOCK_thd_data. + + @param bf_thd brute force THD asking for the abort + @param victim_thd victim THD to be aborted +*/ +static void wsrep_abort_transaction(handlerton *, THD *bf_thd, THD *victim_thd, + my_bool signal) +{ + DBUG_ENTER("wsrep_abort_transaction"); + ut_ad(bf_thd); + ut_ad(victim_thd); + + trx_t *victim_trx= thd_to_trx(victim_thd); + + WSREP_DEBUG("abort transaction: BF: %s victim: %s victim conf: %s", + wsrep_thd_query(bf_thd), wsrep_thd_query(victim_thd), + wsrep_thd_transaction_state_str(victim_thd)); + + if (!victim_trx) + { + WSREP_DEBUG("abort transaction: victim did not exist"); + DBUG_VOID_RETURN; + } + + lock_sys.wr_lock(SRW_LOCK_CALL); + mysql_mutex_lock(&lock_sys.wait_mutex); + victim_trx->mutex_lock(); + + switch (victim_trx->state) { + default: + break; + case TRX_STATE_ACTIVE: + case TRX_STATE_PREPARED: + /* Cancel lock wait if the victim is waiting for a lock in InnoDB. + The transaction which is blocked somewhere else (e.g. waiting + for next command or MDL) has been interrupted by THD::awake_no_mutex() + on server level before calling this function. */ + lock_sys.cancel_lock_wait_for_wsrep_bf_abort(victim_trx); + } + lock_sys.wr_unlock(); + mysql_mutex_unlock(&lock_sys.wait_mutex); + victim_trx->mutex_unlock(); + + DBUG_VOID_RETURN; +} + +static +int +innobase_wsrep_set_checkpoint( +/*==========================*/ + handlerton* hton, + const XID* xid) +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + if (wsrep_is_wsrep_xid(xid)) { + + trx_rseg_update_wsrep_checkpoint(xid); + log_buffer_flush_to_disk(srv_flush_log_at_trx_commit == 1); + return 0; + } else { + return 1; + } +} + +static +int +innobase_wsrep_get_checkpoint( +/*==========================*/ + handlerton* hton, + XID* xid) +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + trx_rseg_read_wsrep_checkpoint(*xid); + return 0; +} +#endif /* WITH_WSREP */ + +/* plugin options */ + +static MYSQL_SYSVAR_ENUM(checksum_algorithm, srv_checksum_algorithm, + PLUGIN_VAR_RQCMDARG, + "The algorithm InnoDB uses for page checksumming. Possible values are" + " FULL_CRC32" + " for new files, always use CRC-32C; for old, see CRC32 below;" + " STRICT_FULL_CRC32" + " for new files, always use CRC-32C; for old, see STRICT_CRC32 below;" + " CRC32" + " write crc32, allow previously used algorithms to match when reading;" + " STRICT_CRC32" + " write crc32, do not allow other algorithms to match when reading;" + " New files created with full_crc32 are readable by MariaDB 10.4.3+", + NULL, NULL, SRV_CHECKSUM_ALGORITHM_FULL_CRC32, + &innodb_checksum_algorithm_typelib); + +static MYSQL_SYSVAR_STR(data_home_dir, innobase_data_home_dir, + PLUGIN_VAR_READONLY, + "The common part for InnoDB table spaces.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_BOOL(doublewrite, srv_use_doublewrite_buf, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Enable InnoDB doublewrite buffer (enabled by default)." + " Disable with --skip-innodb-doublewrite.", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_BOOL(use_atomic_writes, srv_use_atomic_writes, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Enable atomic writes, instead of using the doublewrite buffer, for files " + "on devices that supports atomic writes.", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_BOOL(stats_include_delete_marked, + srv_stats_include_delete_marked, + PLUGIN_VAR_OPCMDARG, + "Include delete marked records when calculating persistent statistics", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_ENUM(instant_alter_column_allowed, + innodb_instant_alter_column_allowed, + PLUGIN_VAR_RQCMDARG, + "File format constraint for ALTER TABLE", NULL, NULL, 2/*add_drop_reorder*/, + &innodb_instant_alter_column_allowed_typelib); + +static MYSQL_SYSVAR_ULONG(io_capacity, srv_io_capacity, + PLUGIN_VAR_RQCMDARG, + "Number of IOPs the server can do. Tunes the background IO rate", + NULL, innodb_io_capacity_update, 200, 100, ~0UL, 0); + +static MYSQL_SYSVAR_ULONG(io_capacity_max, srv_max_io_capacity, + PLUGIN_VAR_RQCMDARG, + "Limit to which innodb_io_capacity can be inflated.", + NULL, innodb_io_capacity_max_update, + SRV_MAX_IO_CAPACITY_DUMMY_DEFAULT, 100, + SRV_MAX_IO_CAPACITY_LIMIT, 0); + +#ifdef UNIV_DEBUG +static MYSQL_SYSVAR_BOOL(log_checkpoint_now, innodb_log_checkpoint_now, + PLUGIN_VAR_OPCMDARG, + "Force checkpoint now", + NULL, checkpoint_now_set, FALSE); + +static MYSQL_SYSVAR_BOOL(buf_flush_list_now, innodb_buf_flush_list_now, + PLUGIN_VAR_OPCMDARG, + "Force dirty page flush now", + NULL, buf_flush_list_now_set, FALSE); + +static MYSQL_SYSVAR_UINT(merge_threshold_set_all_debug, + innodb_merge_threshold_set_all_debug, + PLUGIN_VAR_RQCMDARG, + "Override current MERGE_THRESHOLD setting for all indexes at dictionary" + " cache by the specified value dynamically, at the time.", + NULL, innodb_merge_threshold_set_all_debug_update, + DICT_INDEX_MERGE_THRESHOLD_DEFAULT, 1, 50, 0); +#endif /* UNIV_DEBUG */ + +static MYSQL_SYSVAR_ULONG(purge_batch_size, srv_purge_batch_size, + PLUGIN_VAR_OPCMDARG, + "Number of UNDO log pages to purge in one batch from the history list.", + NULL, NULL, + 1000, /* Default setting */ + 1, /* Minimum value */ + innodb_purge_batch_size_MAX, 0); + +extern void srv_update_purge_thread_count(uint n); + +static +void +innodb_purge_threads_update(THD*, struct st_mysql_sys_var*, void*, const void*save ) +{ + srv_update_purge_thread_count(*static_cast<const uint*>(save)); +} + +static MYSQL_SYSVAR_UINT(purge_threads, srv_n_purge_threads, + PLUGIN_VAR_OPCMDARG, + "Number of tasks for purging transaction history", + NULL, innodb_purge_threads_update, + 4, /* Default setting */ + 1, /* Minimum value */ + innodb_purge_threads_MAX, /* Maximum value */ + 0); + +static MYSQL_SYSVAR_UINT(fast_shutdown, srv_fast_shutdown, + PLUGIN_VAR_OPCMDARG, + "Speeds up the shutdown process of the InnoDB storage engine. Possible" + " values are 0, 1 (faster), 2 (crash-like), 3 (fastest clean).", + fast_shutdown_validate, NULL, 1, 0, 3, 0); + +static MYSQL_SYSVAR_BOOL(file_per_table, srv_file_per_table, + PLUGIN_VAR_NOCMDARG, + "Stores each InnoDB table to an .ibd file in the database dir.", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_STR(ft_server_stopword_table, innobase_server_stopword_table, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_MEMALLOC, + "The user supplied stopword table name.", + innodb_stopword_table_validate, + NULL, + NULL); + +static MYSQL_SYSVAR_UINT(flush_log_at_timeout, srv_flush_log_at_timeout, + PLUGIN_VAR_OPCMDARG, + "Write and flush logs every (n) second.", + NULL, NULL, 1, 0, 2700, 0); + +static MYSQL_SYSVAR_ULONG(flush_log_at_trx_commit, srv_flush_log_at_trx_commit, + PLUGIN_VAR_OPCMDARG, + "Controls the durability/speed trade-off for commits." + " Set to 0 (write and flush redo log to disk only once per second)," + " 1 (flush to disk at each commit)," + " 2 (write to log at commit but flush to disk only once per second)" + " or 3 (flush to disk at prepare and at commit, slower and usually redundant)." + " 1 and 3 guarantees that after a crash, committed transactions will" + " not be lost and will be consistent with the binlog and other transactional" + " engines. 2 can get inconsistent and lose transactions if there is a" + " power failure or kernel crash but not if mysqld crashes. 0 has no" + " guarantees in case of crash. 0 and 2 can be faster than 1 or 3.", + NULL, NULL, 1, 0, 3, 0); + +static MYSQL_SYSVAR_ENUM(flush_method, srv_file_flush_method, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "With which method to flush data.", + NULL, NULL, IF_WIN(SRV_ALL_O_DIRECT_FSYNC, SRV_O_DIRECT), + &innodb_flush_method_typelib); + +static MYSQL_SYSVAR_STR(log_group_home_dir, srv_log_group_home_dir, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path to ib_logfile0", NULL, NULL, NULL); + +static MYSQL_SYSVAR_DOUBLE(max_dirty_pages_pct, srv_max_buf_pool_modified_pct, + PLUGIN_VAR_RQCMDARG, + "Percentage of dirty pages allowed in bufferpool.", + NULL, innodb_max_dirty_pages_pct_update, 90.0, 0, 99.999, 0); + +static MYSQL_SYSVAR_DOUBLE(max_dirty_pages_pct_lwm, + srv_max_dirty_pages_pct_lwm, + PLUGIN_VAR_RQCMDARG, + "Percentage of dirty pages at which flushing kicks in. " + "The value 0 (default) means 'refer to innodb_max_dirty_pages_pct'.", + NULL, innodb_max_dirty_pages_pct_lwm_update, 0, 0, 99.999, 0); + +static MYSQL_SYSVAR_DOUBLE(adaptive_flushing_lwm, + srv_adaptive_flushing_lwm, + PLUGIN_VAR_RQCMDARG, + "Percentage of log capacity below which no adaptive flushing happens.", + NULL, NULL, 10.0, 0.0, 70.0, 0); + +static MYSQL_SYSVAR_BOOL(adaptive_flushing, srv_adaptive_flushing, + PLUGIN_VAR_NOCMDARG, + "Attempt flushing dirty pages to avoid IO bursts at checkpoints.", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_BOOL(flush_sync, srv_flush_sync, + PLUGIN_VAR_NOCMDARG, + "Allow IO bursts at the checkpoints ignoring io_capacity setting.", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_ULONG(flushing_avg_loops, + srv_flushing_avg_loops, + PLUGIN_VAR_RQCMDARG, + "Number of iterations over which the background flushing is averaged.", + NULL, NULL, 30, 1, 1000, 0); + +static MYSQL_SYSVAR_ULONG(max_purge_lag, srv_max_purge_lag, + PLUGIN_VAR_RQCMDARG, + "Desired maximum length of the purge queue (0 = no limit)", + NULL, NULL, 0, 0, ~0UL, 0); + +static MYSQL_SYSVAR_ULONG(max_purge_lag_delay, srv_max_purge_lag_delay, + PLUGIN_VAR_RQCMDARG, + "Maximum delay of user threads in micro-seconds", + NULL, NULL, + 0L, /* Default seting */ + 0L, /* Minimum value */ + 10000000UL, 0); /* Maximum value */ + +static MYSQL_SYSVAR_UINT(max_purge_lag_wait, innodb_max_purge_lag_wait, + PLUGIN_VAR_RQCMDARG, + "Wait until History list length is below the specified limit", + NULL, innodb_max_purge_lag_wait_update, UINT_MAX, 0, UINT_MAX, 0); + +static MYSQL_SYSVAR_BOOL(rollback_on_timeout, innobase_rollback_on_timeout, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, + "Roll back the complete transaction on lock wait timeout, for 4.x compatibility (disabled by default)", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(status_file, innobase_create_status_file, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_NOSYSVAR, + "Enable SHOW ENGINE INNODB STATUS output in the innodb_status.<pid> file", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(stats_on_metadata, innobase_stats_on_metadata, + PLUGIN_VAR_OPCMDARG, + "Enable statistics gathering for metadata commands such as" + " SHOW TABLE STATUS for tables that use transient statistics (off by default)", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_ULONGLONG(stats_transient_sample_pages, + srv_stats_transient_sample_pages, + PLUGIN_VAR_RQCMDARG, + "The number of leaf index pages to sample when calculating transient" + " statistics (if persistent statistics are not used, default 8)", + NULL, NULL, 8, 1, ~0ULL, 0); + +static MYSQL_SYSVAR_BOOL(stats_persistent, srv_stats_persistent, + PLUGIN_VAR_OPCMDARG, + "InnoDB persistent statistics enabled for all tables unless overridden" + " at table level", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_BOOL(stats_auto_recalc, srv_stats_auto_recalc, + PLUGIN_VAR_OPCMDARG, + "InnoDB automatic recalculation of persistent statistics enabled for all" + " tables unless overridden at table level (automatic recalculation is only" + " done when InnoDB decides that the table has changed too much and needs a" + " new statistics)", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_ULONGLONG(stats_persistent_sample_pages, + srv_stats_persistent_sample_pages, + PLUGIN_VAR_RQCMDARG, + "The number of leaf index pages to sample when calculating persistent" + " statistics (by ANALYZE, default 20)", + NULL, NULL, 20, 1, ~0ULL, 0); + +static MYSQL_SYSVAR_ULONGLONG(stats_modified_counter, srv_stats_modified_counter, + PLUGIN_VAR_RQCMDARG, + "The number of rows modified before we calculate new statistics (default 0 = current limits)", + NULL, NULL, 0, 0, ~0ULL, 0); + +static MYSQL_SYSVAR_BOOL(stats_traditional, srv_stats_sample_traditional, + PLUGIN_VAR_RQCMDARG, + "Enable traditional statistic calculation based on number of configured pages (default true)", + NULL, NULL, TRUE); + +#ifdef BTR_CUR_HASH_ADAPT +static MYSQL_SYSVAR_BOOL(adaptive_hash_index, btr_search_enabled, + PLUGIN_VAR_OPCMDARG, + "Enable InnoDB adaptive hash index (disabled by default).", + NULL, innodb_adaptive_hash_index_update, false); + +/** Number of distinct partitions of AHI. +Each partition is protected by its own latch and so we have parts number +of latches protecting complete search system. */ +static MYSQL_SYSVAR_ULONG(adaptive_hash_index_parts, btr_ahi_parts, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, + "Number of InnoDB Adaptive Hash Index Partitions (default 8)", + NULL, NULL, 8, 1, 512, 0); +#endif /* BTR_CUR_HASH_ADAPT */ + +static MYSQL_SYSVAR_UINT(compression_level, page_zip_level, + PLUGIN_VAR_RQCMDARG, + "Compression level used for zlib compression. 0 is no compression" + ", 1 is fastest, 9 is best compression and default is 6.", + NULL, NULL, DEFAULT_COMPRESSION_LEVEL, 0, 9, 0); + +static MYSQL_SYSVAR_UINT(autoextend_increment, + sys_tablespace_auto_extend_increment, + PLUGIN_VAR_RQCMDARG, + "Data file autoextend increment in megabytes", + NULL, NULL, 64, 1, 1000, 0); + +static MYSQL_SYSVAR_SIZE_T(buffer_pool_chunk_size, srv_buf_pool_chunk_unit, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Size of a single memory chunk" + " for resizing buffer pool. Online buffer pool resizing happens at this" + " granularity. 0 means autosize this variable based on buffer pool size.", + NULL, NULL, + 0, 0, SIZE_T_MAX, 1024 * 1024); + +static MYSQL_SYSVAR_STR(buffer_pool_filename, srv_buf_dump_filename, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Filename to/from which to dump/load the InnoDB buffer pool", + NULL, NULL, SRV_BUF_DUMP_FILENAME_DEFAULT); + +static MYSQL_SYSVAR_BOOL(buffer_pool_dump_now, innodb_buffer_pool_dump_now, + PLUGIN_VAR_RQCMDARG, + "Trigger an immediate dump of the buffer pool into a file named @@innodb_buffer_pool_filename", + NULL, buffer_pool_dump_now, FALSE); + +static MYSQL_SYSVAR_BOOL(buffer_pool_dump_at_shutdown, srv_buffer_pool_dump_at_shutdown, + PLUGIN_VAR_RQCMDARG, + "Dump the buffer pool into a file named @@innodb_buffer_pool_filename", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_ULONG(buffer_pool_dump_pct, srv_buf_pool_dump_pct, + PLUGIN_VAR_RQCMDARG, + "Dump only the hottest N% of each buffer pool, defaults to 25", + NULL, NULL, 25, 1, 100, 0); + +#ifdef UNIV_DEBUG +/* Added to test the innodb_buffer_pool_load_incomplete status variable. */ +static MYSQL_SYSVAR_ULONG(buffer_pool_load_pages_abort, srv_buf_pool_load_pages_abort, + PLUGIN_VAR_RQCMDARG, + "Number of pages during a buffer pool load to process before signaling innodb_buffer_pool_load_abort=1", + NULL, NULL, LONG_MAX, 1, LONG_MAX, 0); + +static MYSQL_SYSVAR_STR(buffer_pool_evict, srv_buffer_pool_evict, + PLUGIN_VAR_RQCMDARG, + "Evict pages from the buffer pool", + NULL, innodb_buffer_pool_evict_update, ""); +#endif /* UNIV_DEBUG */ + +static MYSQL_SYSVAR_BOOL(buffer_pool_load_now, innodb_buffer_pool_load_now, + PLUGIN_VAR_RQCMDARG, + "Trigger an immediate load of the buffer pool from a file named @@innodb_buffer_pool_filename", + NULL, buffer_pool_load_now, FALSE); + +static MYSQL_SYSVAR_BOOL(buffer_pool_load_abort, innodb_buffer_pool_load_abort, + PLUGIN_VAR_RQCMDARG, + "Abort a currently running load of the buffer pool", + NULL, buffer_pool_load_abort, FALSE); + +/* there is no point in changing this during runtime, thus readonly */ +static MYSQL_SYSVAR_BOOL(buffer_pool_load_at_startup, srv_buffer_pool_load_at_startup, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Load the buffer pool from a file named @@innodb_buffer_pool_filename", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_BOOL(defragment, srv_defragment, + PLUGIN_VAR_RQCMDARG, + "Enable/disable InnoDB defragmentation (default FALSE). When set to FALSE, all existing " + "defragmentation will be paused. And new defragmentation command will fail." + "Paused defragmentation commands will resume when this variable is set to " + "true again.", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_UINT(defragment_n_pages, srv_defragment_n_pages, + PLUGIN_VAR_RQCMDARG, + "Number of pages considered at once when merging multiple pages to " + "defragment", + NULL, NULL, 7, 2, 32, 0); + +static MYSQL_SYSVAR_UINT(defragment_stats_accuracy, + srv_defragment_stats_accuracy, + PLUGIN_VAR_RQCMDARG, + "How many defragment stats changes there are before the stats " + "are written to persistent storage. Set to 0 meaning disable " + "defragment stats tracking.", + NULL, NULL, 0, 0, ~0U, 0); + +static MYSQL_SYSVAR_UINT(defragment_fill_factor_n_recs, + srv_defragment_fill_factor_n_recs, + PLUGIN_VAR_RQCMDARG, + "How many records of space defragmentation should leave on the page. " + "This variable, together with innodb_defragment_fill_factor, is introduced " + "so defragmentation won't pack the page too full and cause page split on " + "the next insert on every page. The variable indicating more defragmentation" + " gain is the one effective.", + NULL, NULL, 20, 1, 100, 0); + +static MYSQL_SYSVAR_DOUBLE(defragment_fill_factor, srv_defragment_fill_factor, + PLUGIN_VAR_RQCMDARG, + "A number between [0.7, 1] that tells defragmentation how full it should " + "fill a page. Default is 0.9. Number below 0.7 won't make much sense." + "This variable, together with innodb_defragment_fill_factor_n_recs, is " + "introduced so defragmentation won't pack the page too full and cause " + "page split on the next insert on every page. The variable indicating more " + "defragmentation gain is the one effective.", + NULL, NULL, 0.9, 0.7, 1, 0); + +static MYSQL_SYSVAR_UINT(defragment_frequency, srv_defragment_frequency, + PLUGIN_VAR_RQCMDARG, + "Do not defragment a single index more than this number of time per second." + "This controls the number of time defragmentation thread can request X_LOCK " + "on an index. Defragmentation thread will check whether " + "1/defragment_frequency (s) has passed since it worked on this index last " + "time, and put the index back to the queue if not enough time has passed. " + "The actual frequency can only be lower than this given number.", + NULL, innodb_defragment_frequency_update, + SRV_DEFRAGMENT_FREQUENCY_DEFAULT, 1, 1000, 0); + + +static MYSQL_SYSVAR_ULONG(lru_scan_depth, srv_LRU_scan_depth, + PLUGIN_VAR_RQCMDARG, + "How deep to scan LRU to keep it clean", + NULL, NULL, 1536, 100, ~0UL, 0); + +static MYSQL_SYSVAR_SIZE_T(lru_flush_size, innodb_lru_flush_size, + PLUGIN_VAR_RQCMDARG, + "How many pages to flush on LRU eviction", + NULL, NULL, 32, 1, SIZE_T_MAX, 0); + +static MYSQL_SYSVAR_ULONG(flush_neighbors, srv_flush_neighbors, + PLUGIN_VAR_OPCMDARG, + "Set to 0 (don't flush neighbors from buffer pool)," + " 1 (flush contiguous neighbors from buffer pool)" + " or 2 (flush neighbors from buffer pool)," + " when flushing a block", + NULL, NULL, 1, 0, 2, 0); + +static MYSQL_SYSVAR_BOOL(deadlock_detect, innodb_deadlock_detect, + PLUGIN_VAR_NOCMDARG, + "Enable/disable InnoDB deadlock detector (default ON)." + " if set to OFF, deadlock detection is skipped," + " and we rely on innodb_lock_wait_timeout in case of deadlock.", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_ENUM(deadlock_report, innodb_deadlock_report, + PLUGIN_VAR_RQCMDARG, + "How to report deadlocks (if innodb_deadlock_detect=ON).", + NULL, NULL, Deadlock::REPORT_FULL, &innodb_deadlock_report_typelib); + +static MYSQL_SYSVAR_UINT(fill_factor, innobase_fill_factor, + PLUGIN_VAR_RQCMDARG, + "Percentage of B-tree page filled during bulk insert", + NULL, NULL, 100, 10, 100, 0); + +static MYSQL_SYSVAR_BOOL(ft_enable_diag_print, fts_enable_diag_print, + PLUGIN_VAR_OPCMDARG, + "Whether to enable additional FTS diagnostic printout ", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(disable_sort_file_cache, srv_disable_sort_file_cache, + PLUGIN_VAR_OPCMDARG, + "Whether to disable OS system file cache for sort I/O", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_STR(ft_aux_table, innodb_ft_aux_table, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC, + "FTS internal auxiliary table to be checked", + innodb_ft_aux_table_validate, NULL, NULL); + +#if UNIV_WORD_SIZE == 4 + +static MYSQL_SYSVAR_SIZE_T(ft_cache_size, + *reinterpret_cast<size_t*>(&fts_max_cache_size), + PLUGIN_VAR_RQCMDARG, + "InnoDB Fulltext search cache size in bytes", + NULL, innodb_ft_cache_size_update, 8000000, 1600000, 1U << 29, 0); + +static MYSQL_SYSVAR_SIZE_T(ft_total_cache_size, + *reinterpret_cast<size_t*>(&fts_max_total_cache_size), + PLUGIN_VAR_RQCMDARG, + "Total memory allocated for InnoDB Fulltext Search cache", + NULL, innodb_ft_total_cache_size_update, 640000000, 32000000, 1600000000, 0); + +#else + +static MYSQL_SYSVAR_SIZE_T(ft_cache_size, + *reinterpret_cast<size_t*>(&fts_max_cache_size), + PLUGIN_VAR_RQCMDARG, + "InnoDB Fulltext search cache size in bytes", + NULL, innodb_ft_cache_size_update, 8000000, 1600000, 1ULL << 40, 0); + +static MYSQL_SYSVAR_SIZE_T(ft_total_cache_size, + *reinterpret_cast<size_t*>(&fts_max_total_cache_size), + PLUGIN_VAR_RQCMDARG, + "Total memory allocated for InnoDB Fulltext Search cache", + NULL, innodb_ft_total_cache_size_update, 640000000, 32000000, 1ULL << 40, 0); + +#endif + +static MYSQL_SYSVAR_SIZE_T(ft_result_cache_limit, fts_result_cache_limit, + PLUGIN_VAR_RQCMDARG, + "InnoDB Fulltext search query result cache limit in bytes", + NULL, NULL, 2000000000L, 1000000L, SIZE_T_MAX, 0); + +static MYSQL_SYSVAR_ULONG(ft_min_token_size, fts_min_token_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "InnoDB Fulltext search minimum token size in characters", + NULL, NULL, 3, 0, 16, 0); + +static MYSQL_SYSVAR_ULONG(ft_max_token_size, fts_max_token_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "InnoDB Fulltext search maximum token size in characters", + NULL, NULL, FTS_MAX_WORD_LEN_IN_CHAR, 10, FTS_MAX_WORD_LEN_IN_CHAR, 0); + +static MYSQL_SYSVAR_ULONG(ft_num_word_optimize, fts_num_word_optimize, + PLUGIN_VAR_OPCMDARG, + "InnoDB Fulltext search number of words to optimize for each optimize table call ", + NULL, NULL, 2000, 1000, 10000, 0); + +static MYSQL_SYSVAR_ULONG(ft_sort_pll_degree, fts_sort_pll_degree, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "InnoDB Fulltext search parallel sort degree, will round up to nearest power of 2 number", + NULL, NULL, 2, 1, 16, 0); + +static MYSQL_SYSVAR_ULONG(sort_buffer_size, srv_sort_buf_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Memory buffer size for index creation", + NULL, NULL, 1048576, 65536, 64<<20, 0); + +static MYSQL_SYSVAR_ULONGLONG(online_alter_log_max_size, srv_online_max_size, + PLUGIN_VAR_RQCMDARG, + "Maximum modification log file size for online index creation", + NULL, NULL, 128<<20, 65536, ~0ULL, 0); + +static MYSQL_SYSVAR_BOOL(optimize_fulltext_only, innodb_optimize_fulltext_only, + PLUGIN_VAR_NOCMDARG, + "Only optimize the Fulltext index of the table", + NULL, NULL, FALSE); + +extern int os_aio_resize(ulint n_reader_threads, ulint n_writer_threads); +static void innodb_update_io_thread_count(THD *thd,ulint n_read, ulint n_write) +{ + int res = os_aio_resize(n_read, n_write); + if (res) + { +#ifndef __linux__ + ut_ad(0); +#else + ut_a(srv_use_native_aio); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_UNKNOWN_ERROR, + "Could not reserve max. number of concurrent ios." + "Increase the /proc/sys/fs/aio-max-nr to fix."); +#endif + } +} + +static void innodb_read_io_threads_update(THD* thd, struct st_mysql_sys_var*, void*, const void* save) +{ + srv_n_read_io_threads = *static_cast<const uint*>(save); + innodb_update_io_thread_count(thd, srv_n_read_io_threads, srv_n_write_io_threads); +} +static void innodb_write_io_threads_update(THD* thd, struct st_mysql_sys_var*, void*, const void* save) +{ + srv_n_write_io_threads = *static_cast<const uint*>(save); + innodb_update_io_thread_count(thd, srv_n_read_io_threads, srv_n_write_io_threads); +} + +static MYSQL_SYSVAR_UINT(read_io_threads, srv_n_read_io_threads, + PLUGIN_VAR_RQCMDARG, + "Number of background read I/O threads in InnoDB.", + NULL, innodb_read_io_threads_update , 4, 1, 64, 0); + +static MYSQL_SYSVAR_UINT(write_io_threads, srv_n_write_io_threads, + PLUGIN_VAR_RQCMDARG, + "Number of background write I/O threads in InnoDB.", + NULL, innodb_write_io_threads_update, 4, 2, 64, 0); + +static MYSQL_SYSVAR_ULONG(force_recovery, srv_force_recovery, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Helps to save your data in case the disk image of the database becomes corrupt. Value 5 can return bogus data, and 6 can permanently corrupt data.", + NULL, NULL, 0, 0, 6, 0); + +static MYSQL_SYSVAR_ULONG(page_size, srv_page_size, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, + "Page size to use for all InnoDB tablespaces.", + NULL, NULL, UNIV_PAGE_SIZE_DEF, + UNIV_PAGE_SIZE_MIN, UNIV_PAGE_SIZE_MAX, 0); + +static MYSQL_SYSVAR_SIZE_T(log_buffer_size, log_sys.buf_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Redo log buffer size in bytes.", + NULL, NULL, 16U << 20, 2U << 20, SIZE_T_MAX, 4096); + +#if defined __linux__ || defined _WIN32 +static MYSQL_SYSVAR_BOOL(log_file_buffering, log_sys.log_buffered, + PLUGIN_VAR_OPCMDARG, + "Whether the file system cache for ib_logfile0 is enabled", + nullptr, innodb_log_file_buffering_update, FALSE); +#endif + +static MYSQL_SYSVAR_ULONGLONG(log_file_size, srv_log_file_size, + PLUGIN_VAR_RQCMDARG, + "Redo log size in bytes.", + nullptr, innodb_log_file_size_update, + 96 << 20, 4 << 20, std::numeric_limits<ulonglong>::max(), 4096); + +static MYSQL_SYSVAR_UINT(old_blocks_pct, innobase_old_blocks_pct, + PLUGIN_VAR_RQCMDARG, + "Percentage of the buffer pool to reserve for 'old' blocks.", + NULL, innodb_old_blocks_pct_update, 100 * 3 / 8, 5, 95, 0); + +static MYSQL_SYSVAR_UINT(old_blocks_time, buf_LRU_old_threshold_ms, + PLUGIN_VAR_RQCMDARG, + "Move blocks to the 'new' end of the buffer pool if the first access" + " was at least this many milliseconds ago." + " The timeout is disabled if 0.", + NULL, NULL, 1000, 0, UINT_MAX32, 0); + +static MYSQL_SYSVAR_ULONG(open_files, innobase_open_files, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "How many files at the maximum InnoDB keeps open at the same time.", + NULL, NULL, 0, 0, LONG_MAX, 0); + +static MYSQL_SYSVAR_ULONG(sync_spin_loops, srv_n_spin_wait_rounds, + PLUGIN_VAR_RQCMDARG, + "Count of spin-loop rounds in InnoDB mutexes (30 by default)", + NULL, NULL, 30L, 0L, ~0UL, 0); + +static MYSQL_SYSVAR_UINT(spin_wait_delay, srv_spin_wait_delay, + PLUGIN_VAR_OPCMDARG, + "Maximum delay between polling for a spin lock (4 by default)", + NULL, NULL, 4, 0, 6000, 0); + +static my_bool innodb_prefix_index_cluster_optimization; + +static MYSQL_SYSVAR_BOOL(prefix_index_cluster_optimization, + innodb_prefix_index_cluster_optimization, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_DEPRECATED, + "Deprecated parameter with no effect", + nullptr, nullptr, TRUE); + +static MYSQL_SYSVAR_STR(data_file_path, innobase_data_file_path, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path to individual files and their sizes.", + NULL, NULL, "ibdata1:12M:autoextend"); + +static MYSQL_SYSVAR_STR(temp_data_file_path, innobase_temp_data_file_path, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path to files and their sizes making temp-tablespace.", + NULL, NULL, "ibtmp1:12M:autoextend"); + +static MYSQL_SYSVAR_STR(undo_directory, srv_undo_dir, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Directory where undo tablespace files live, this path can be absolute.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_UINT(undo_tablespaces, srv_undo_tablespaces, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Number of undo tablespaces to use.", + NULL, NULL, + 0L, /* Default seting */ + 0L, /* Minimum value */ + TRX_SYS_MAX_UNDO_SPACES, 0); /* Maximum value */ + +static MYSQL_SYSVAR_ULONGLONG(max_undo_log_size, srv_max_undo_log_size, + PLUGIN_VAR_OPCMDARG, + "Desired maximum UNDO tablespace size in bytes", + NULL, NULL, + 10 << 20, 10 << 20, + 1ULL << (32 + UNIV_PAGE_SIZE_SHIFT_MAX), 0); + +static MYSQL_SYSVAR_ULONG(purge_rseg_truncate_frequency, + srv_purge_rseg_truncate_frequency, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_DEPRECATED, + "Deprecated parameter with no effect", + NULL, NULL, 128, 1, 128, 0); + +static void innodb_undo_log_truncate_update(THD *thd, struct st_mysql_sys_var*, + void*, const void *save) +{ + if ((srv_undo_log_truncate= *static_cast<const my_bool*>(save))) + purge_sys.wake_if_not_active(); +} + +static MYSQL_SYSVAR_BOOL(undo_log_truncate, srv_undo_log_truncate, + PLUGIN_VAR_OPCMDARG, + "Enable or Disable Truncate of UNDO tablespace.", + NULL, innodb_undo_log_truncate_update, FALSE); + +static MYSQL_SYSVAR_LONG(autoinc_lock_mode, innobase_autoinc_lock_mode, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "The AUTOINC lock modes supported by InnoDB:" + " 0 => Old style AUTOINC locking (for backward compatibility);" + " 1 => New style AUTOINC locking;" + " 2 => No AUTOINC locking (unsafe for SBR)", + NULL, NULL, + AUTOINC_NEW_STYLE_LOCKING, /* Default setting */ + AUTOINC_OLD_STYLE_LOCKING, /* Minimum value */ + AUTOINC_NO_LOCKING, 0); /* Maximum value */ + +#ifdef HAVE_URING +# include <sys/utsname.h> +static utsname uname_for_io_uring; +#else +static +#endif +bool innodb_use_native_aio_default() +{ +#ifdef HAVE_URING + utsname &u= uname_for_io_uring; + if (!uname(&u) && u.release[0] == '5' && u.release[1] == '.' && + u.release[2] == '1' && u.release[3] >= '1' && u.release[3] <= '5' && + u.release[4] == '.') + { + if (u.release[3] == '5') { + const char *s= strstr(u.version, "5.15."); + if (s || (s= strstr(u.release, "5.15."))) + if ((s[5] >= '3' || s[6] >= '0')) + return true; /* 5.15.3 and later should be fine */ + } + io_uring_may_be_unsafe= u.release; + return false; /* working around io_uring hangs (MDEV-26674) */ + } +#endif + return true; +} + +static MYSQL_SYSVAR_BOOL(use_native_aio, srv_use_native_aio, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Use native AIO if supported on this platform.", + NULL, NULL, innodb_use_native_aio_default()); + +#ifdef HAVE_LIBNUMA +static MYSQL_SYSVAR_BOOL(numa_interleave, srv_numa_interleave, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Use NUMA interleave memory policy to allocate InnoDB buffer pool.", + NULL, NULL, FALSE); +#endif /* HAVE_LIBNUMA */ + +static void innodb_change_buffering_update(THD *thd, struct st_mysql_sys_var*, + void*, const void *save) +{ + ulong i= *static_cast<const ulong*>(save); + if (i != IBUF_USE_NONE && !ibuf.index) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: The change buffer is corrupted."); + else + innodb_change_buffering= i; +} + +static MYSQL_SYSVAR_ENUM(change_buffering, innodb_change_buffering, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_DEPRECATED, + "Buffer changes to secondary indexes.", + nullptr, innodb_change_buffering_update, + IBUF_USE_NONE, &innodb_change_buffering_typelib); + +static MYSQL_SYSVAR_UINT(change_buffer_max_size, + srv_change_buffer_max_size, + PLUGIN_VAR_RQCMDARG, + "Maximum on-disk size of change buffer in terms of percentage" + " of the buffer pool.", + NULL, innodb_change_buffer_max_size_update, + CHANGE_BUFFER_DEFAULT_SIZE, 0, 50, 0); + +static MYSQL_SYSVAR_ENUM(stats_method, srv_innodb_stats_method, + PLUGIN_VAR_RQCMDARG, + "Specifies how InnoDB index statistics collection code should" + " treat NULLs. Possible values are NULLS_EQUAL (default)," + " NULLS_UNEQUAL and NULLS_IGNORED", + NULL, NULL, SRV_STATS_NULLS_EQUAL, &innodb_stats_method_typelib); + +#if defined UNIV_DEBUG || defined UNIV_IBUF_DEBUG +static MYSQL_SYSVAR_BOOL(change_buffer_dump, ibuf_dump, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Dump the change buffer at startup.", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_UINT(change_buffering_debug, ibuf_debug, + PLUGIN_VAR_RQCMDARG, + "Debug flags for InnoDB change buffering (0=none, 1=try to buffer)", + NULL, NULL, 0, 0, 1, 0); +#endif /* UNIV_DEBUG || UNIV_IBUF_DEBUG */ + +static MYSQL_SYSVAR_ULONG(buf_dump_status_frequency, srv_buf_dump_status_frequency, + PLUGIN_VAR_RQCMDARG, + "A number between [0, 100] that tells how oftern buffer pool dump status " + "in percentages should be printed. E.g. 10 means that buffer pool dump " + "status is printed when every 10% of number of buffer pool pages are " + "dumped. Default is 0 (only start and end status is printed).", + NULL, NULL, 0, 0, 100, 0); + +static MYSQL_SYSVAR_BOOL(random_read_ahead, srv_random_read_ahead, + PLUGIN_VAR_NOCMDARG, + "Whether to use read ahead for random access within an extent.", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_ULONG(read_ahead_threshold, srv_read_ahead_threshold, + PLUGIN_VAR_RQCMDARG, + "Number of pages that must be accessed sequentially for InnoDB to" + " trigger a readahead.", + NULL, NULL, 56, 0, 64, 0); + +static MYSQL_SYSVAR_STR(monitor_enable, innobase_enable_monitor_counter, + PLUGIN_VAR_RQCMDARG, + "Turn on a monitor counter", + innodb_monitor_validate, + innodb_enable_monitor_update, NULL); + +static MYSQL_SYSVAR_STR(monitor_disable, innobase_disable_monitor_counter, + PLUGIN_VAR_RQCMDARG, + "Turn off a monitor counter", + innodb_monitor_validate, + innodb_disable_monitor_update, NULL); + +static MYSQL_SYSVAR_STR(monitor_reset, innobase_reset_monitor_counter, + PLUGIN_VAR_RQCMDARG, + "Reset a monitor counter", + innodb_monitor_validate, + innodb_reset_monitor_update, NULL); + +static MYSQL_SYSVAR_STR(monitor_reset_all, innobase_reset_all_monitor_counter, + PLUGIN_VAR_RQCMDARG, + "Reset all values for a monitor counter", + innodb_monitor_validate, + innodb_reset_all_monitor_update, NULL); + +static MYSQL_SYSVAR_BOOL(status_output, srv_print_innodb_monitor, + PLUGIN_VAR_OPCMDARG, "Enable InnoDB monitor output to the error log.", + NULL, innodb_status_output_update, FALSE); + +static MYSQL_SYSVAR_BOOL(status_output_locks, srv_print_innodb_lock_monitor, + PLUGIN_VAR_OPCMDARG, "Enable InnoDB lock monitor output to the error log." + " Requires innodb_status_output=ON.", + NULL, innodb_status_output_update, FALSE); + +static MYSQL_SYSVAR_BOOL(print_all_deadlocks, srv_print_all_deadlocks, + PLUGIN_VAR_OPCMDARG, + "Print all deadlocks to MariaDB error log (off by default)", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_ULONG(compression_failure_threshold_pct, + zip_failure_threshold_pct, PLUGIN_VAR_OPCMDARG, + "If the compression failure rate of a table is greater than this number" + " more padding is added to the pages to reduce the failures. A value of" + " zero implies no padding", + NULL, NULL, 5, 0, 100, 0); + +static MYSQL_SYSVAR_ULONG(compression_pad_pct_max, + zip_pad_max, PLUGIN_VAR_OPCMDARG, + "Percentage of empty space on a data page that can be reserved" + " to make the page compressible.", + NULL, NULL, 50, 0, 75, 0); + +static MYSQL_SYSVAR_BOOL(read_only, srv_read_only_mode, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, + "Start InnoDB in read only mode (off by default)", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(read_only_compressed, innodb_read_only_compressed, + PLUGIN_VAR_OPCMDARG, + "Make ROW_FORMAT=COMPRESSED tables read-only", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(cmp_per_index_enabled, srv_cmp_per_index_enabled, + PLUGIN_VAR_OPCMDARG, + "Enable INFORMATION_SCHEMA.innodb_cmp_per_index," + " may have negative impact on performance (off by default)", + NULL, innodb_cmp_per_index_update, FALSE); + +static MYSQL_SYSVAR_ENUM(default_row_format, innodb_default_row_format, + PLUGIN_VAR_RQCMDARG, + "The default ROW FORMAT for all innodb tables created without explicit" + " ROW_FORMAT. Possible values are REDUNDANT, COMPACT, and DYNAMIC." + " The ROW_FORMAT value COMPRESSED is not allowed", + NULL, NULL, DEFAULT_ROW_FORMAT_DYNAMIC, + &innodb_default_row_format_typelib); + +#ifdef UNIV_DEBUG +static MYSQL_SYSVAR_UINT(trx_rseg_n_slots_debug, trx_rseg_n_slots_debug, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_NOCMDOPT, + "Debug flags for InnoDB to limit TRX_RSEG_N_SLOTS for trx_rsegf_undo_find_free()", + NULL, NULL, 0, 0, 1024, 0); + +static MYSQL_SYSVAR_UINT(limit_optimistic_insert_debug, + btr_cur_limit_optimistic_insert_debug, PLUGIN_VAR_RQCMDARG, + "Artificially limit the number of records per B-tree page (0=unlimited).", + NULL, NULL, 0, 0, UINT_MAX32, 0); + +static MYSQL_SYSVAR_BOOL(trx_purge_view_update_only_debug, + srv_purge_view_update_only_debug, PLUGIN_VAR_NOCMDOPT, + "Pause actual purging any delete-marked records, but merely update the purge view." + " It is to create artificially the situation the purge view have been updated" + " but the each purges were not done yet.", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(evict_tables_on_commit_debug, + innodb_evict_tables_on_commit_debug, PLUGIN_VAR_OPCMDARG, + "On transaction commit, try to evict tables from the data dictionary cache.", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_UINT(data_file_size_debug, + srv_sys_space_size_debug, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "InnoDB system tablespace size to be set in recovery.", + NULL, NULL, 0, 0, 256U << 20, 0); + +static MYSQL_SYSVAR_UINT(fil_make_page_dirty_debug, + srv_fil_make_page_dirty_debug, PLUGIN_VAR_OPCMDARG, + "Make the first page of the given tablespace dirty.", + NULL, innodb_make_page_dirty, 0, 0, UINT_MAX32, 0); + +static MYSQL_SYSVAR_UINT(saved_page_number_debug, + srv_saved_page_number_debug, PLUGIN_VAR_OPCMDARG, + "An InnoDB page number.", + NULL, NULL, 0, 0, UINT_MAX32, 0); +#endif /* UNIV_DEBUG */ + +static MYSQL_SYSVAR_BOOL(force_primary_key, + srv_force_primary_key, + PLUGIN_VAR_OPCMDARG, + "Do not allow creating a table without primary key (off by default)", + NULL, NULL, FALSE); + +const char *page_compression_algorithms[]= { "none", "zlib", "lz4", "lzo", "lzma", "bzip2", "snappy", 0 }; +static TYPELIB page_compression_algorithms_typelib= +{ + array_elements(page_compression_algorithms) - 1, 0, + page_compression_algorithms, 0 +}; +static MYSQL_SYSVAR_ENUM(compression_algorithm, innodb_compression_algorithm, + PLUGIN_VAR_OPCMDARG, + "Compression algorithm used on page compression. One of: none, zlib, lz4, lzo, lzma, bzip2, or snappy", + innodb_compression_algorithm_validate, NULL, + /* We use here the largest number of supported compression method to + enable all those methods that are available. Availability of compression + method is verified on innodb_compression_algorithm_validate function. */ + PAGE_ZLIB_ALGORITHM, + &page_compression_algorithms_typelib); + +static MYSQL_SYSVAR_ULONG(fatal_semaphore_wait_threshold, srv_fatal_semaphore_wait_threshold, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Maximum number of seconds that semaphore times out in InnoDB.", + NULL, NULL, + DEFAULT_SRV_FATAL_SEMAPHORE_TIMEOUT, /* Default setting */ + 1, /* Minimum setting */ + UINT_MAX32, /* Maximum setting */ + 0); + +static const char* srv_encrypt_tables_names[] = { "OFF", "ON", "FORCE", 0 }; +static TYPELIB srv_encrypt_tables_typelib = { + array_elements(srv_encrypt_tables_names)-1, 0, srv_encrypt_tables_names, + NULL +}; +static MYSQL_SYSVAR_ENUM(encrypt_tables, srv_encrypt_tables, + PLUGIN_VAR_OPCMDARG, + "Enable encryption for tables. " + "Don't forget to enable --innodb-encrypt-log too", + innodb_encrypt_tables_validate, + innodb_encrypt_tables_update, + 0, + &srv_encrypt_tables_typelib); + +static MYSQL_SYSVAR_UINT(encryption_threads, srv_n_fil_crypt_threads, + PLUGIN_VAR_RQCMDARG, + "Number of threads performing background key rotation ", + NULL, + innodb_encryption_threads_update, + 0, 0, 255, 0); + +static MYSQL_SYSVAR_UINT(encryption_rotate_key_age, + srv_fil_crypt_rotate_key_age, + PLUGIN_VAR_RQCMDARG, + "Key rotation - re-encrypt in background " + "all pages that were encrypted with a key that " + "many (or more) versions behind. Value 0 indicates " + "that key rotation is disabled.", + NULL, + innodb_encryption_rotate_key_age_update, + 1, 0, UINT_MAX32, 0); + +static MYSQL_SYSVAR_UINT(encryption_rotation_iops, srv_n_fil_crypt_iops, + PLUGIN_VAR_RQCMDARG, + "Use this many iops for background key rotation", + NULL, + innodb_encryption_rotation_iops_update, + 100, 0, UINT_MAX32, 0); + +static MYSQL_SYSVAR_BOOL(encrypt_log, srv_encrypt_log, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, + "Enable redo log encryption", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(immediate_scrub_data_uncompressed, + srv_immediate_scrub_data_uncompressed, + 0, + "Enable scrubbing of data", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(encrypt_temporary_tables, innodb_encrypt_temporary_tables, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, + "Enrypt the temporary table data.", + NULL, NULL, false); + +static struct st_mysql_sys_var* innobase_system_variables[]= { + MYSQL_SYSVAR(autoextend_increment), + MYSQL_SYSVAR(buffer_pool_size), + MYSQL_SYSVAR(buffer_pool_chunk_size), + MYSQL_SYSVAR(buffer_pool_filename), + MYSQL_SYSVAR(buffer_pool_dump_now), + MYSQL_SYSVAR(buffer_pool_dump_at_shutdown), + MYSQL_SYSVAR(buffer_pool_dump_pct), +#ifdef UNIV_DEBUG + MYSQL_SYSVAR(buffer_pool_evict), +#endif /* UNIV_DEBUG */ + MYSQL_SYSVAR(buffer_pool_load_now), + MYSQL_SYSVAR(buffer_pool_load_abort), +#ifdef UNIV_DEBUG + MYSQL_SYSVAR(buffer_pool_load_pages_abort), +#endif /* UNIV_DEBUG */ + MYSQL_SYSVAR(buffer_pool_load_at_startup), + MYSQL_SYSVAR(defragment), + MYSQL_SYSVAR(defragment_n_pages), + MYSQL_SYSVAR(defragment_stats_accuracy), + MYSQL_SYSVAR(defragment_fill_factor), + MYSQL_SYSVAR(defragment_fill_factor_n_recs), + MYSQL_SYSVAR(defragment_frequency), + MYSQL_SYSVAR(lru_scan_depth), + MYSQL_SYSVAR(lru_flush_size), + MYSQL_SYSVAR(flush_neighbors), + MYSQL_SYSVAR(checksum_algorithm), + MYSQL_SYSVAR(compression_level), + MYSQL_SYSVAR(data_file_path), + MYSQL_SYSVAR(temp_data_file_path), + MYSQL_SYSVAR(data_home_dir), + MYSQL_SYSVAR(doublewrite), + MYSQL_SYSVAR(stats_include_delete_marked), + MYSQL_SYSVAR(use_atomic_writes), + MYSQL_SYSVAR(fast_shutdown), + MYSQL_SYSVAR(read_io_threads), + MYSQL_SYSVAR(write_io_threads), + MYSQL_SYSVAR(file_per_table), + MYSQL_SYSVAR(flush_log_at_timeout), + MYSQL_SYSVAR(flush_log_at_trx_commit), + MYSQL_SYSVAR(flush_method), + MYSQL_SYSVAR(force_recovery), + MYSQL_SYSVAR(fill_factor), + MYSQL_SYSVAR(ft_cache_size), + MYSQL_SYSVAR(ft_total_cache_size), + MYSQL_SYSVAR(ft_result_cache_limit), + MYSQL_SYSVAR(ft_enable_stopword), + MYSQL_SYSVAR(ft_max_token_size), + MYSQL_SYSVAR(ft_min_token_size), + MYSQL_SYSVAR(ft_num_word_optimize), + MYSQL_SYSVAR(ft_sort_pll_degree), + MYSQL_SYSVAR(lock_wait_timeout), + MYSQL_SYSVAR(deadlock_detect), + MYSQL_SYSVAR(deadlock_report), + MYSQL_SYSVAR(page_size), + MYSQL_SYSVAR(log_buffer_size), +#if defined __linux__ || defined _WIN32 + MYSQL_SYSVAR(log_file_buffering), +#endif + MYSQL_SYSVAR(log_file_size), + MYSQL_SYSVAR(log_group_home_dir), + MYSQL_SYSVAR(max_dirty_pages_pct), + MYSQL_SYSVAR(max_dirty_pages_pct_lwm), + MYSQL_SYSVAR(adaptive_flushing_lwm), + MYSQL_SYSVAR(adaptive_flushing), + MYSQL_SYSVAR(flush_sync), + MYSQL_SYSVAR(flushing_avg_loops), + MYSQL_SYSVAR(max_purge_lag), + MYSQL_SYSVAR(max_purge_lag_delay), + MYSQL_SYSVAR(max_purge_lag_wait), + MYSQL_SYSVAR(old_blocks_pct), + MYSQL_SYSVAR(old_blocks_time), + MYSQL_SYSVAR(open_files), + MYSQL_SYSVAR(optimize_fulltext_only), + MYSQL_SYSVAR(rollback_on_timeout), + MYSQL_SYSVAR(ft_aux_table), + MYSQL_SYSVAR(ft_enable_diag_print), + MYSQL_SYSVAR(ft_server_stopword_table), + MYSQL_SYSVAR(ft_user_stopword_table), + MYSQL_SYSVAR(disable_sort_file_cache), + MYSQL_SYSVAR(stats_on_metadata), + MYSQL_SYSVAR(stats_transient_sample_pages), + MYSQL_SYSVAR(stats_persistent), + MYSQL_SYSVAR(stats_persistent_sample_pages), + MYSQL_SYSVAR(stats_auto_recalc), + MYSQL_SYSVAR(stats_modified_counter), + MYSQL_SYSVAR(stats_traditional), +#ifdef BTR_CUR_HASH_ADAPT + MYSQL_SYSVAR(adaptive_hash_index), + MYSQL_SYSVAR(adaptive_hash_index_parts), +#endif /* BTR_CUR_HASH_ADAPT */ + MYSQL_SYSVAR(stats_method), + MYSQL_SYSVAR(status_file), + MYSQL_SYSVAR(strict_mode), + MYSQL_SYSVAR(sort_buffer_size), + MYSQL_SYSVAR(online_alter_log_max_size), + MYSQL_SYSVAR(sync_spin_loops), + MYSQL_SYSVAR(spin_wait_delay), + MYSQL_SYSVAR(table_locks), + MYSQL_SYSVAR(prefix_index_cluster_optimization), + MYSQL_SYSVAR(tmpdir), + MYSQL_SYSVAR(autoinc_lock_mode), + MYSQL_SYSVAR(use_native_aio), +#ifdef HAVE_LIBNUMA + MYSQL_SYSVAR(numa_interleave), +#endif /* HAVE_LIBNUMA */ + MYSQL_SYSVAR(change_buffering), + MYSQL_SYSVAR(change_buffer_max_size), +#if defined UNIV_DEBUG || defined UNIV_IBUF_DEBUG + MYSQL_SYSVAR(change_buffer_dump), + MYSQL_SYSVAR(change_buffering_debug), +#endif /* UNIV_DEBUG || UNIV_IBUF_DEBUG */ + MYSQL_SYSVAR(random_read_ahead), + MYSQL_SYSVAR(read_ahead_threshold), + MYSQL_SYSVAR(read_only), + MYSQL_SYSVAR(read_only_compressed), + MYSQL_SYSVAR(instant_alter_column_allowed), + MYSQL_SYSVAR(io_capacity), + MYSQL_SYSVAR(io_capacity_max), + MYSQL_SYSVAR(monitor_enable), + MYSQL_SYSVAR(monitor_disable), + MYSQL_SYSVAR(monitor_reset), + MYSQL_SYSVAR(monitor_reset_all), + MYSQL_SYSVAR(purge_threads), + MYSQL_SYSVAR(purge_batch_size), +#ifdef UNIV_DEBUG + MYSQL_SYSVAR(log_checkpoint_now), + MYSQL_SYSVAR(buf_flush_list_now), + MYSQL_SYSVAR(merge_threshold_set_all_debug), +#endif /* UNIV_DEBUG */ + MYSQL_SYSVAR(status_output), + MYSQL_SYSVAR(status_output_locks), + MYSQL_SYSVAR(print_all_deadlocks), + MYSQL_SYSVAR(cmp_per_index_enabled), + MYSQL_SYSVAR(max_undo_log_size), + MYSQL_SYSVAR(purge_rseg_truncate_frequency), + MYSQL_SYSVAR(undo_log_truncate), + MYSQL_SYSVAR(undo_directory), + MYSQL_SYSVAR(undo_tablespaces), + MYSQL_SYSVAR(compression_failure_threshold_pct), + MYSQL_SYSVAR(compression_pad_pct_max), + MYSQL_SYSVAR(default_row_format), +#ifdef UNIV_DEBUG + MYSQL_SYSVAR(trx_rseg_n_slots_debug), + MYSQL_SYSVAR(limit_optimistic_insert_debug), + MYSQL_SYSVAR(trx_purge_view_update_only_debug), + MYSQL_SYSVAR(evict_tables_on_commit_debug), + MYSQL_SYSVAR(data_file_size_debug), + MYSQL_SYSVAR(fil_make_page_dirty_debug), + MYSQL_SYSVAR(saved_page_number_debug), +#endif /* UNIV_DEBUG */ + MYSQL_SYSVAR(force_primary_key), + MYSQL_SYSVAR(fatal_semaphore_wait_threshold), + /* Table page compression feature */ + MYSQL_SYSVAR(compression_default), + MYSQL_SYSVAR(compression_algorithm), + /* Encryption feature */ + MYSQL_SYSVAR(encrypt_tables), + MYSQL_SYSVAR(encryption_threads), + MYSQL_SYSVAR(encryption_rotate_key_age), + MYSQL_SYSVAR(encryption_rotation_iops), + MYSQL_SYSVAR(encrypt_log), + MYSQL_SYSVAR(default_encryption_key_id), + MYSQL_SYSVAR(immediate_scrub_data_uncompressed), + MYSQL_SYSVAR(buf_dump_status_frequency), + MYSQL_SYSVAR(background_thread), + MYSQL_SYSVAR(encrypt_temporary_tables), + + NULL +}; + +maria_declare_plugin(innobase) +{ + MYSQL_STORAGE_ENGINE_PLUGIN, + &innobase_storage_engine, + innobase_hton_name, + plugin_author, + "Supports transactions, row-level locking, foreign keys and encryption for tables", + PLUGIN_LICENSE_GPL, + innodb_init, /* Plugin Init */ + NULL, /* Plugin Deinit */ + MYSQL_VERSION_MAJOR << 8 | MYSQL_VERSION_MINOR, + innodb_status_variables_export,/* status variables */ + innobase_system_variables, /* system variables */ + PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE /* maturity */ +}, +i_s_innodb_trx, +i_s_innodb_locks, +i_s_innodb_lock_waits, +i_s_innodb_cmp, +i_s_innodb_cmp_reset, +i_s_innodb_cmpmem, +i_s_innodb_cmpmem_reset, +i_s_innodb_cmp_per_index, +i_s_innodb_cmp_per_index_reset, +i_s_innodb_buffer_page, +i_s_innodb_buffer_page_lru, +i_s_innodb_buffer_stats, +i_s_innodb_metrics, +i_s_innodb_ft_default_stopword, +i_s_innodb_ft_deleted, +i_s_innodb_ft_being_deleted, +i_s_innodb_ft_config, +i_s_innodb_ft_index_cache, +i_s_innodb_ft_index_table, +i_s_innodb_sys_tables, +i_s_innodb_sys_tablestats, +i_s_innodb_sys_indexes, +i_s_innodb_sys_columns, +i_s_innodb_sys_fields, +i_s_innodb_sys_foreign, +i_s_innodb_sys_foreign_cols, +i_s_innodb_sys_tablespaces, +i_s_innodb_sys_virtual, +i_s_innodb_tablespaces_encryption +maria_declare_plugin_end; + +/** @brief Adjust some InnoDB startup parameters based on file contents +or innodb_page_size. */ +static +void +innodb_params_adjust() +{ + MYSQL_SYSVAR_NAME(max_undo_log_size).max_val + = 1ULL << (32U + srv_page_size_shift); + MYSQL_SYSVAR_NAME(max_undo_log_size).min_val + = MYSQL_SYSVAR_NAME(max_undo_log_size).def_val + = ulonglong(SRV_UNDO_TABLESPACE_SIZE_IN_PAGES) + << srv_page_size_shift; + MYSQL_SYSVAR_NAME(max_undo_log_size).max_val + = 1ULL << (32U + srv_page_size_shift); +} + +/**************************************************************************** + * DS-MRR implementation + ***************************************************************************/ + +/** +Multi Range Read interface, DS-MRR calls */ +int +ha_innobase::multi_range_read_init( + RANGE_SEQ_IF* seq, + void* seq_init_param, + uint n_ranges, + uint mode, + HANDLER_BUFFER* buf) +{ + return(m_ds_mrr.dsmrr_init(this, seq, seq_init_param, + n_ranges, mode, buf)); +} + +int +ha_innobase::multi_range_read_next( + range_id_t* range_info) +{ + return(m_ds_mrr.dsmrr_next(range_info)); +} + +ha_rows +ha_innobase::multi_range_read_info_const( + uint keyno, + RANGE_SEQ_IF* seq, + void* seq_init_param, + uint n_ranges, + uint* bufsz, + uint* flags, + Cost_estimate* cost) +{ + /* See comments in ha_myisam::multi_range_read_info_const */ + m_ds_mrr.init(this, table); + + if (m_prebuilt->select_lock_type != LOCK_NONE) { + *flags |= HA_MRR_USE_DEFAULT_IMPL; + } + + ha_rows res= m_ds_mrr.dsmrr_info_const(keyno, seq, seq_init_param, n_ranges, + bufsz, flags, cost); + return res; +} + +ha_rows +ha_innobase::multi_range_read_info( + uint keyno, + uint n_ranges, + uint keys, + uint key_parts, + uint* bufsz, + uint* flags, + Cost_estimate* cost) +{ + m_ds_mrr.init(this, table); + ha_rows res= m_ds_mrr.dsmrr_info(keyno, n_ranges, keys, key_parts, bufsz, + flags, cost); + return res; +} + +int +ha_innobase::multi_range_read_explain_info( + uint mrr_mode, + char *str, + size_t size) +{ + return m_ds_mrr.dsmrr_explain_info(mrr_mode, str, size); +} + +/** Find or open a table handle for the virtual column template +@param[in] thd thread handle +@param[in,out] table InnoDB table whose virtual column template + is to be updated +@return table handle +@retval NULL if the table is dropped, unaccessible or corrupted +for purge thread */ +static TABLE* innodb_find_table_for_vc(THD* thd, dict_table_t* table) +{ + TABLE *mysql_table; + const bool bg_thread = THDVAR(thd, background_thread); + + if (bg_thread) { + if ((mysql_table = get_purge_table(thd))) { + return mysql_table; + } + } else { + if (table->vc_templ->mysql_table_query_id + == thd_get_query_id(thd)) { + return table->vc_templ->mysql_table; + } + } + + char db_buf[NAME_LEN + 1]; + char tbl_buf[NAME_LEN + 1]; + ulint db_buf_len, tbl_buf_len; + + if (!table->parse_name(db_buf, tbl_buf, &db_buf_len, &tbl_buf_len)) { + return NULL; + } + + if (bg_thread) { + return open_purge_table(thd, db_buf, db_buf_len, + tbl_buf, tbl_buf_len); + } + + mysql_table = find_fk_open_table(thd, db_buf, db_buf_len, + tbl_buf, tbl_buf_len); + table->vc_templ->mysql_table = mysql_table; + table->vc_templ->mysql_table_query_id = thd_get_query_id(thd); + return mysql_table; +} + +/** Change dbname and table name in table->vc_templ. +@param[in,out] table the table whose virtual column template +dbname and tbname to be renamed. */ +void +innobase_rename_vc_templ( + dict_table_t* table) +{ + char dbname[MAX_DATABASE_NAME_LEN + 1]; + char tbname[MAX_DATABASE_NAME_LEN + 1]; + char* name = table->name.m_name; + ulint dbnamelen = dict_get_db_name_len(name); + ulint tbnamelen = strlen(name) - dbnamelen - 1; + char t_dbname[MAX_DATABASE_NAME_LEN + 1]; + char t_tbname[MAX_TABLE_NAME_LEN + 1]; + + strncpy(dbname, name, dbnamelen); + dbname[dbnamelen] = 0; + strncpy(tbname, name + dbnamelen + 1, tbnamelen); + tbname[tbnamelen] =0; + + /* For partition table, remove the partition name and use the + "main" table name to build the template */ + char* is_part = is_partition(tbname); + + if (is_part != NULL) { + *is_part = '\0'; + tbnamelen = ulint(is_part - tbname); + } + + dbnamelen = filename_to_tablename(dbname, t_dbname, + MAX_DATABASE_NAME_LEN + 1); + tbnamelen = filename_to_tablename(tbname, t_tbname, + MAX_TABLE_NAME_LEN + 1); + + table->vc_templ->db_name = t_dbname; + table->vc_templ->tb_name = t_tbname; +} + + +/** + Allocate a heap and record for calculating virtual fields + Used mainly for virtual fields in indexes + +@param[in] thd MariaDB THD +@param[in] index Index in use +@param[out] heap Heap that holds temporary row +@param[in,out] table MariaDB table +@param[out] record Pointer to allocated MariaDB record +@param[out] storage Internal storage for blobs etc + +@retval true on success +@retval false on malloc failure or failed to open the maria table + for purge thread. +*/ + +bool innobase_allocate_row_for_vcol(THD *thd, const dict_index_t *index, + mem_heap_t **heap, TABLE **table, + VCOL_STORAGE *storage) +{ + TABLE *maria_table; + String *blob_value_storage; + if (!*table) + *table = innodb_find_table_for_vc(thd, index->table); + + /* For purge thread, there is a possiblity that table could have + dropped, corrupted or unaccessible. */ + if (!*table) + return false; + maria_table = *table; + if (!*heap && !(*heap = mem_heap_create(srv_page_size))) + return false; + + uchar *record = static_cast<byte *>(mem_heap_alloc(*heap, + maria_table->s->reclength)); + + size_t len = maria_table->s->virtual_not_stored_blob_fields * sizeof(String); + blob_value_storage = static_cast<String *>(mem_heap_alloc(*heap, len)); + + if (!record || !blob_value_storage) + return false; + + storage->maria_table = maria_table; + storage->innobase_record = record; + storage->maria_record = maria_table->field[0]->record_ptr(); + storage->blob_value_storage = blob_value_storage; + + maria_table->move_fields(maria_table->field, record, storage->maria_record); + maria_table->remember_blob_values(blob_value_storage); + + return true; +} + + +/** Free memory allocated by innobase_allocate_row_for_vcol() */ + +void innobase_free_row_for_vcol(VCOL_STORAGE *storage) +{ + TABLE *maria_table= storage->maria_table; + maria_table->move_fields(maria_table->field, storage->maria_record, + storage->innobase_record); + maria_table->restore_blob_values(storage->blob_value_storage); +} + + +void innobase_report_computed_value_failed(dtuple_t *row) +{ + ib::error() << "Compute virtual column values failed for " + << rec_printer(row).str(); +} + + +/** Get the computed value by supplying the base column values. +@param[in,out] row the data row +@param[in] col virtual column +@param[in] index index +@param[in,out] local_heap heap memory for processing large data etc. +@param[in,out] heap memory heap that copies the actual index row +@param[in] ifield index field +@param[in] thd MySQL thread handle +@param[in,out] mysql_table mysql table object +@param[in,out] mysql_rec MariaDB record buffer +@param[in] old_table during ALTER TABLE, this is the old table + or NULL. +@param[in] update update vector for the row, if any +@param[in] foreign foreign key information +@return the field filled with computed value, or NULL if just want +to store the value in passed in "my_rec" */ +dfield_t* +innobase_get_computed_value( + dtuple_t* row, + const dict_v_col_t* col, + const dict_index_t* index, + mem_heap_t** local_heap, + mem_heap_t* heap, + const dict_field_t* ifield, + THD* thd, + TABLE* mysql_table, + byte* mysql_rec, + const dict_table_t* old_table, + const upd_t* update, + bool ignore_warnings) +{ + byte rec_buf2[REC_VERSION_56_MAX_INDEX_COL_LEN]; + byte* buf; + dfield_t* field; + ulint len; + + const ulint zip_size = old_table + ? old_table->space->zip_size() + : dict_tf_get_zip_size(index->table->flags); + + ulint ret = 0; + + dict_index_t *clust_index= dict_table_get_first_index(index->table); + + ut_ad(index->table->vc_templ); + ut_ad(thd != NULL); + ut_ad(mysql_table); + + DBUG_ENTER("innobase_get_computed_value"); + const mysql_row_templ_t* + vctempl = index->table->vc_templ->vtempl[ + index->table->vc_templ->n_col + col->v_pos]; + + if (!heap || index->table->vc_templ->rec_len + >= REC_VERSION_56_MAX_INDEX_COL_LEN) { + if (*local_heap == NULL) { + *local_heap = mem_heap_create(srv_page_size); + } + + buf = static_cast<byte*>(mem_heap_alloc( + *local_heap, index->table->vc_templ->rec_len)); + } else { + buf = rec_buf2; + } + + for (ulint i = 0; i < unsigned{col->num_base}; i++) { + dict_col_t* base_col = col->base_col[i]; + const dfield_t* row_field = NULL; + ulint col_no = base_col->ind; + const mysql_row_templ_t* templ + = index->table->vc_templ->vtempl[col_no]; + const byte* data; + + if (update) { + ulint clust_no = dict_col_get_clust_pos(base_col, + clust_index); + ut_ad(clust_no != ULINT_UNDEFINED); + if (const upd_field_t *uf = upd_get_field_by_field_no( + update, uint16_t(clust_no), false)) { + row_field = &uf->new_val; + } + } + + if (!row_field) { + row_field = dtuple_get_nth_field(row, col_no); + } + + data = static_cast<const byte*>(row_field->data); + len = row_field->len; + + if (row_field->ext) { + if (*local_heap == NULL) { + *local_heap = mem_heap_create(srv_page_size); + } + + data = btr_copy_externally_stored_field( + &len, data, zip_size, + dfield_get_len(row_field), *local_heap); + } + + if (len == UNIV_SQL_NULL) { +#if defined __GNUC__ && !defined __clang__ && __GNUC__ < 6 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" /* GCC 5 may need this here */ +#endif + mysql_rec[templ->mysql_null_byte_offset] + |= (byte) templ->mysql_null_bit_mask; +#if defined __GNUC__ && !defined __clang__ && __GNUC__ < 6 +# pragma GCC diagnostic pop +#endif + memcpy(mysql_rec + templ->mysql_col_offset, + static_cast<const byte*>( + index->table->vc_templ->default_rec + + templ->mysql_col_offset), + templ->mysql_col_len); + } else { + + row_sel_field_store_in_mysql_format( + mysql_rec + templ->mysql_col_offset, + templ, index, templ->clust_rec_field_no, + (const byte*)data, len); + + if (templ->mysql_null_bit_mask) { + /* It is a nullable column with a + non-NULL value */ + mysql_rec[templ->mysql_null_byte_offset] + &= static_cast<byte>( + ~templ->mysql_null_bit_mask); + } + } + } + + field = dtuple_get_nth_v_field(row, col->v_pos); + + MY_BITMAP *old_write_set = dbug_tmp_use_all_columns(mysql_table, &mysql_table->write_set); + MY_BITMAP *old_read_set = dbug_tmp_use_all_columns(mysql_table, &mysql_table->read_set); + ret = mysql_table->update_virtual_field( + mysql_table->field[col->m_col.ind], + ignore_warnings); + dbug_tmp_restore_column_map(&mysql_table->read_set, old_read_set); + dbug_tmp_restore_column_map(&mysql_table->write_set, old_write_set); + + if (ret != 0) { + DBUG_RETURN(NULL); + } + + if (vctempl->mysql_null_bit_mask + && (mysql_rec[vctempl->mysql_null_byte_offset] + & vctempl->mysql_null_bit_mask)) { + dfield_set_null(field); + field->type.prtype |= DATA_VIRTUAL; + DBUG_RETURN(field); + } + + row_mysql_store_col_in_innobase_format( + field, buf, + TRUE, mysql_rec + vctempl->mysql_col_offset, + vctempl->mysql_col_len, dict_table_is_comp(index->table)); + field->type.prtype |= DATA_VIRTUAL; + + ulint max_prefix = col->m_col.max_prefix; + + if (max_prefix && ifield + && (ifield->prefix_len == 0 + || ifield->prefix_len > col->m_col.max_prefix)) { + max_prefix = ifield->prefix_len; + } + + /* If this is a prefix index, we only need a portion of the field */ + if (max_prefix) { + len = dtype_get_at_most_n_mbchars( + col->m_col.prtype, + col->m_col.mbminlen, col->m_col.mbmaxlen, + max_prefix, + field->len, + static_cast<char*>(dfield_get_data(field))); + dfield_set_len(field, len); + } + + if (heap) { + dfield_dup(field, heap); + } + + DBUG_RETURN(field); +} + + +/** Attempt to push down an index condition. +@param[in] keyno MySQL key number +@param[in] idx_cond Index condition to be checked +@return Part of idx_cond which the handler will not evaluate */ + +class Item* +ha_innobase::idx_cond_push( + uint keyno, + class Item* idx_cond) +{ + DBUG_ENTER("ha_innobase::idx_cond_push"); + DBUG_ASSERT(keyno != MAX_KEY); + DBUG_ASSERT(idx_cond != NULL); + + /* We can only evaluate the condition if all columns are stored.*/ + dict_index_t* idx = innobase_get_index(keyno); + if (idx && dict_index_has_virtual(idx)) { + DBUG_RETURN(idx_cond); + } + + pushed_idx_cond = idx_cond; + pushed_idx_cond_keyno = keyno; + in_range_check_pushed_down = TRUE; + /* We will evaluate the condition entirely */ + DBUG_RETURN(NULL); +} + + +/** Push a primary key filter. +@param[in] pk_filter filter against which primary keys + are to be checked +@retval false if pushed (always) */ +bool ha_innobase::rowid_filter_push(Rowid_filter* pk_filter) +{ + DBUG_ENTER("ha_innobase::rowid_filter_push"); + DBUG_ASSERT(pk_filter != NULL); + pushed_rowid_filter= pk_filter; + DBUG_RETURN(false); +} + +static bool is_part_of_a_key_prefix(const Field_longstr *field) +{ + const TABLE_SHARE *s= field->table->s; + + for (uint i= 0; i < s->keys; i++) + { + const KEY &key= s->key_info[i]; + for (uint j= 0; j < key.user_defined_key_parts; j++) + { + const KEY_PART_INFO &info= key.key_part[j]; + // When field is a part of some key, a key part and field will have the + // same length. And their length will be different when only some prefix + // of a field is used as a key part. That's what we're looking for here. + if (info.field->field_index == field->field_index && + info.length != field->field_length) + { + DBUG_ASSERT(info.length < field->field_length); + return true; + } + } + } + + return false; +} + +static bool +is_part_of_a_primary_key(const Field* field) +{ + const TABLE_SHARE* s = field->table->s; + + return s->primary_key != MAX_KEY + && field->part_of_key.is_set(s->primary_key); +} + +bool ha_innobase::can_convert_string(const Field_string *field, + const Column_definition &new_type) const +{ + DBUG_ASSERT(!field->compression_method()); + if (new_type.type_handler() != field->type_handler()) + return false; + + if (new_type.char_length != field->char_length()) + return false; + + const Charset field_cs(field->charset()); + + if (new_type.length != field->max_display_length() && + (!m_prebuilt->table->not_redundant() || + field_cs.mbminlen() == field_cs.mbmaxlen())) + return false; + + if (new_type.charset != field->charset()) + { + if (!field_cs.encoding_allows_reinterpret_as(new_type.charset)) + return false; + + if (!field_cs.eq_collation_specific_names(new_type.charset)) + return !is_part_of_a_primary_key(field); + + // Fully indexed case works instantly like + // Compare_keys::EqualButKeyPartLength. But prefix case isn't implemented. + if (is_part_of_a_key_prefix(field)) + return false; + + return true; + } + + return true; +} + +static bool +supports_enlarging(const dict_table_t* table, const Field_varstring* field, + const Column_definition& new_type) +{ + return field->field_length <= 127 || new_type.length <= 255 + || field->field_length > 255 || !table->not_redundant(); +} + +bool ha_innobase::can_convert_varstring( + const Field_varstring *field, const Column_definition &new_type) const +{ + if (new_type.length < field->field_length) + return false; + + if (new_type.char_length < field->char_length()) + return false; + + if (!new_type.compression_method() != !field->compression_method()) + return false; + + if (new_type.type_handler() != field->type_handler()) + return false; + + if (new_type.charset != field->charset()) + { + if (!supports_enlarging(m_prebuilt->table, field, new_type)) + return false; + + Charset field_cs(field->charset()); + if (!field_cs.encoding_allows_reinterpret_as(new_type.charset)) + return false; + + if (!field_cs.eq_collation_specific_names(new_type.charset)) + return !is_part_of_a_primary_key(field); + + // Fully indexed case works instantly like + // Compare_keys::EqualButKeyPartLength. But prefix case isn't implemented. + if (is_part_of_a_key_prefix(field)) + return false; + + return true; + } + + if (new_type.length != field->field_length) + { + if (!supports_enlarging(m_prebuilt->table, field, new_type)) + return false; + + return true; + } + + return true; +} + +static bool is_part_of_a_key(const Field_blob *field) +{ + const TABLE_SHARE *s= field->table->s; + + for (uint i= 0; i < s->keys; i++) + { + const KEY &key= s->key_info[i]; + for (uint j= 0; j < key.user_defined_key_parts; j++) + { + const KEY_PART_INFO &info= key.key_part[j]; + if (info.field->field_index == field->field_index) + return true; + } + } + + return false; +} + +bool ha_innobase::can_convert_blob(const Field_blob *field, + const Column_definition &new_type) const +{ + if (new_type.type_handler() != field->type_handler()) + return false; + + if (!new_type.compression_method() != !field->compression_method()) + return false; + + if (new_type.pack_length != field->pack_length()) + return false; + + if (new_type.charset != field->charset()) + { + Charset field_cs(field->charset()); + if (!field_cs.encoding_allows_reinterpret_as(new_type.charset)) + return false; + + if (!field_cs.eq_collation_specific_names(new_type.charset)) + return !is_part_of_a_key(field); + + // Fully indexed case works instantly like + // Compare_keys::EqualButKeyPartLength. But prefix case isn't implemented. + if (is_part_of_a_key_prefix(field)) + return false; + + return true; + } + + return true; +} + + +bool ha_innobase::can_convert_nocopy(const Field &field, + const Column_definition &new_type) const +{ + if (const Field_string *tf= dynamic_cast<const Field_string *>(&field)) + return can_convert_string(tf, new_type); + + if (const Field_varstring *tf= dynamic_cast<const Field_varstring *>(&field)) + return can_convert_varstring(tf, new_type); + + if (dynamic_cast<const Field_geom *>(&field)) + return false; + + if (const Field_blob *tf= dynamic_cast<const Field_blob *>(&field)) + return can_convert_blob(tf, new_type); + + return false; +} + + +Compare_keys ha_innobase::compare_key_parts( + const Field &old_field, const Column_definition &new_field, + const KEY_PART_INFO &old_part, const KEY_PART_INFO &new_part) const +{ + const bool is_equal= old_field.is_equal(new_field); + const CHARSET_INFO *old_cs= old_field.charset(); + const CHARSET_INFO *new_cs= new_field.charset; + + if (!is_equal) + { + if (!old_field.table->file->can_convert_nocopy(old_field, new_field)) + return Compare_keys::NotEqual; + + if (!Charset(old_cs).eq_collation_specific_names(new_cs)) + return Compare_keys::NotEqual; + } + + if (old_part.length / old_cs->mbmaxlen != new_part.length / new_cs->mbmaxlen) + { + if (old_part.length != old_field.field_length) + return Compare_keys::NotEqual; + + if (old_part.length >= new_part.length) + return Compare_keys::NotEqual; + + return Compare_keys::EqualButKeyPartLength; + } + + return Compare_keys::Equal; +} + +/******************************************************************//** +Use this when the args are passed to the format string from +errmsg-utf8.txt directly as is. + +Push a warning message to the client, it is a wrapper around: + +void push_warning_printf( + THD *thd, Sql_condition::enum_condition_level level, + uint code, const char *format, ...); +*/ +void +ib_senderrf( +/*========*/ + THD* thd, /*!< in/out: session */ + ib_log_level_t level, /*!< in: warning level */ + ib_uint32_t code, /*!< MySQL error code */ + ...) /*!< Args */ +{ + va_list args; + const char* format = my_get_err_msg(code); + + /* If the caller wants to push a message to the client then + the caller must pass a valid session handle. */ + + ut_a(thd != 0); + + /* The error code must exist in the errmsg-utf8.txt file. */ + ut_a(format != 0); + + va_start(args, code); + + myf l; + + switch (level) { + case IB_LOG_LEVEL_INFO: + l = ME_NOTE; + break; + case IB_LOG_LEVEL_WARN: + l = ME_WARNING; + break; + default: + l = 0; + break; + } + + my_printv_error(code, format, MYF(l), args); + + va_end(args); + + if (level == IB_LOG_LEVEL_FATAL) { + ut_error; + } +} + +/******************************************************************//** +Use this when the args are first converted to a formatted string and then +passed to the format string from errmsg-utf8.txt. The error message format +must be: "Some string ... %s". + +Push a warning message to the client, it is a wrapper around: + +void push_warning_printf( + THD *thd, Sql_condition::enum_condition_level level, + uint code, const char *format, ...); +*/ +void +ib_errf( +/*====*/ + THD* thd, /*!< in/out: session */ + ib_log_level_t level, /*!< in: warning level */ + ib_uint32_t code, /*!< MySQL error code */ + const char* format, /*!< printf format */ + ...) /*!< Args */ +{ + char* str = NULL; + va_list args; + + /* If the caller wants to push a message to the client then + the caller must pass a valid session handle. */ + + ut_a(thd != 0); + ut_a(format != 0); + + va_start(args, format); + +#ifdef _WIN32 + int size = _vscprintf(format, args) + 1; + if (size > 0) { + str = static_cast<char*>(malloc(size)); + } + if (str == NULL) { + va_end(args); + return; /* Watch for Out-Of-Memory */ + } + str[size - 1] = 0x0; + vsnprintf(str, size, format, args); +#elif HAVE_VASPRINTF + if (vasprintf(&str, format, args) == -1) { + /* In case of failure use a fixed length string */ + str = static_cast<char*>(malloc(BUFSIZ)); + vsnprintf(str, BUFSIZ, format, args); + } +#else + /* Use a fixed length string. */ + str = static_cast<char*>(malloc(BUFSIZ)); + if (str == NULL) { + va_end(args); + return; /* Watch for Out-Of-Memory */ + } + vsnprintf(str, BUFSIZ, format, args); +#endif /* _WIN32 */ + + ib_senderrf(thd, level, code, str); + + va_end(args); + free(str); +} + +/* Keep the first 16 characters as-is, since the url is sometimes used +as an offset from this.*/ +const char* TROUBLESHOOTING_MSG = + "Please refer to https://mariadb.com/kb/en/innodb-troubleshooting/" + " for how to resolve the issue."; + +const char* TROUBLESHOOT_DATADICT_MSG = + "Please refer to https://mariadb.com/kb/en/innodb-data-dictionary-troubleshooting/" + " for how to resolve the issue."; + +const char* BUG_REPORT_MSG = + "Submit a detailed bug report to https://jira.mariadb.org/"; + +const char* FORCE_RECOVERY_MSG = + "Please refer to " + "https://mariadb.com/kb/en/library/innodb-recovery-modes/" + " for information about forcing recovery."; + +const char* OPERATING_SYSTEM_ERROR_MSG = + "Some operating system error numbers are described at" + " https://mariadb.com/kb/en/library/operating-system-error-codes/"; + +const char* FOREIGN_KEY_CONSTRAINTS_MSG = + "Please refer to https://mariadb.com/kb/en/library/foreign-keys/" + " for correct foreign key definition."; + +const char* SET_TRANSACTION_MSG = + "Please refer to https://mariadb.com/kb/en/library/set-transaction/"; + +const char* INNODB_PARAMETERS_MSG = + "Please refer to https://mariadb.com/kb/en/library/innodb-system-variables/"; + +/********************************************************************** +Converts an identifier from my_charset_filename to UTF-8 charset. +@return result string length, as returned by strconvert() */ +uint +innobase_convert_to_filename_charset( +/*=================================*/ + char* to, /* out: converted identifier */ + const char* from, /* in: identifier to convert */ + ulint len) /* in: length of 'to', in bytes */ +{ + uint errors; + CHARSET_INFO* cs_to = &my_charset_filename; + CHARSET_INFO* cs_from = system_charset_info; + + return(static_cast<uint>(strconvert( + cs_from, from, uint(strlen(from)), + cs_to, to, static_cast<uint>(len), &errors))); +} + +/********************************************************************** +Converts an identifier from my_charset_filename to UTF-8 charset. +@return result string length, as returned by strconvert() */ +uint +innobase_convert_to_system_charset( +/*===============================*/ + char* to, /* out: converted identifier */ + const char* from, /* in: identifier to convert */ + ulint len, /* in: length of 'to', in bytes */ + uint* errors) /* out: error return */ +{ + CHARSET_INFO* cs1 = &my_charset_filename; + CHARSET_INFO* cs2 = system_charset_info; + + return(static_cast<uint>(strconvert( + cs1, from, static_cast<uint>(strlen(from)), + cs2, to, static_cast<uint>(len), errors))); +} + +/** Validate the requested buffer pool size. Also, reserve the necessary +memory needed for buffer pool resize. +@param[in] thd thread handle +@param[out] save immediate result for update function +@param[in] value incoming string +@return 0 on success, 1 on failure. +*/ +static +int +innodb_buffer_pool_size_validate( + THD* thd, + st_mysql_sys_var*, + void* save, + struct st_mysql_value* value) +{ + longlong intbuf; + + value->val_int(value, &intbuf); + + if (static_cast<ulonglong>(intbuf) < MYSQL_SYSVAR_NAME(buffer_pool_size).min_val) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "innodb_buffer_pool_size must be at least" + " %lld for innodb_page_size=%lu", + MYSQL_SYSVAR_NAME(buffer_pool_size).min_val, + srv_page_size); + return(1); + } + + if (!srv_was_started) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "Cannot update innodb_buffer_pool_size," + " because InnoDB is not started."); + return(1); + } + + mysql_mutex_lock(&buf_pool.mutex); + + if (srv_buf_pool_old_size != srv_buf_pool_size) { + mysql_mutex_unlock(&buf_pool.mutex); + my_printf_error(ER_WRONG_ARGUMENTS, + "Another buffer pool resize is already in progress.", MYF(0)); + return(1); + } + + ulint requested_buf_pool_size = buf_pool_size_align(ulint(intbuf)); + + *static_cast<ulonglong*>(save) = requested_buf_pool_size; + + if (srv_buf_pool_size == ulint(intbuf)) { + mysql_mutex_unlock(&buf_pool.mutex); + /* nothing to do */ + return(0); + } + + if (srv_buf_pool_size == requested_buf_pool_size) { + mysql_mutex_unlock(&buf_pool.mutex); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "innodb_buffer_pool_size must be at least" + " innodb_buffer_pool_chunk_size=%zu", + srv_buf_pool_chunk_unit); + /* nothing to do */ + return(0); + } + + srv_buf_pool_size = requested_buf_pool_size; + mysql_mutex_unlock(&buf_pool.mutex); + + if (intbuf != static_cast<longlong>(requested_buf_pool_size)) { + char buf[64]; + int len = 64; + value->val_str(value, buf, &len); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE, + "Truncated incorrect %-.32s value: '%-.128s'", + mysql_sysvar_buffer_pool_size.name, + value->val_str(value, buf, &len)); + } + + return(0); +} + +/*************************************************************//** +Check for a valid value of innobase_compression_algorithm. +@return 0 for valid innodb_compression_algorithm. */ +static +int +innodb_compression_algorithm_validate( +/*==================================*/ + THD* thd, /*!< in: thread handle */ + struct st_mysql_sys_var* var, /*!< in: pointer to system + variable */ + void* save, /*!< out: immediate result + for update function */ + struct st_mysql_value* value) /*!< in: incoming string */ +{ + DBUG_ENTER("innobase_compression_algorithm_validate"); + + if (check_sysvar_enum(thd, var, save, value)) { + DBUG_RETURN(1); + } + + if (compression_algorithm_is_not_loaded(*(ulong*)save, ME_WARNING)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + +static +int +innodb_encrypt_tables_validate( +/*=================================*/ + THD* thd, /*!< in: thread handle */ + struct st_mysql_sys_var* var, /*!< in: pointer to system + variable */ + void* save, /*!< out: immediate result + for update function */ + struct st_mysql_value* value) /*!< in: incoming string */ +{ + if (check_sysvar_enum(thd, var, save, value)) { + return 1; + } + + ulong encrypt_tables = *(ulong*)save; + + if (encrypt_tables + && !encryption_key_id_exists(FIL_DEFAULT_ENCRYPTION_KEY)) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + HA_ERR_UNSUPPORTED, + "InnoDB: cannot enable encryption, " + "encryption plugin is not available"); + return 1; + } + + return 0; +} + +static void innodb_remember_check_sysvar_funcs() +{ + /* remember build-in sysvar check functions */ + ut_ad((MYSQL_SYSVAR_NAME(checksum_algorithm).flags & 0x1FF) == PLUGIN_VAR_ENUM); + check_sysvar_enum = MYSQL_SYSVAR_NAME(checksum_algorithm).check; + + ut_ad((MYSQL_SYSVAR_NAME(flush_log_at_timeout).flags & 15) == PLUGIN_VAR_INT); + check_sysvar_int = MYSQL_SYSVAR_NAME(flush_log_at_timeout).check; +} + +static const size_t MAX_BUF_SIZE = 4 * 1024; + +/********************************************************************//** +Helper function to push warnings from InnoDB internals to SQL-layer. */ +void +ib_push_warning( + trx_t* trx, /*!< in: trx */ + dberr_t error, /*!< in: error code to push as warning */ + const char *format,/*!< in: warning message */ + ...) +{ + if (trx && trx->mysql_thd) { + THD *thd = (THD *)trx->mysql_thd; + va_list args; + char *buf; + + va_start(args, format); + buf = (char *)my_malloc(PSI_INSTRUMENT_ME, MAX_BUF_SIZE, MYF(MY_WME)); + buf[MAX_BUF_SIZE - 1] = 0; + vsnprintf(buf, MAX_BUF_SIZE - 1, format, args); + + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + uint(convert_error_code_to_mysql(error, 0, thd)), buf); + my_free(buf); + va_end(args); + } +} + +/********************************************************************//** +Helper function to push warnings from InnoDB internals to SQL-layer. */ +void +ib_push_warning( + void* ithd, /*!< in: thd */ + dberr_t error, /*!< in: error code to push as warning */ + const char *format,/*!< in: warning message */ + ...) +{ + va_list args; + THD *thd = (THD *)ithd; + char *buf; + + if (ithd == NULL) { + thd = current_thd; + } + + if (thd) { + va_start(args, format); + buf = (char *)my_malloc(PSI_INSTRUMENT_ME, MAX_BUF_SIZE, MYF(MY_WME)); + buf[MAX_BUF_SIZE - 1] = 0; + vsnprintf(buf, MAX_BUF_SIZE - 1, format, args); + + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + uint(convert_error_code_to_mysql(error, 0, thd)), buf); + my_free(buf); + va_end(args); + } +} + +/** Helper function to push warnings from InnoDB internals to SQL-layer. +@param[in] trx +@param[in] error Error code to push as warning +@param[in] table_name Table name +@param[in] format Warning message +@param[in] ... Message arguments */ +void +ib_foreign_warn(trx_t* trx, /*!< in: trx */ + dberr_t error, /*!< in: error code to push as warning */ + const char* table_name, + const char* format, /*!< in: warning message */ + ...) +{ + va_list args; + char* buf; + static FILE* ef = dict_foreign_err_file; + static const size_t MAX_BUF_SIZE = 4 * 1024; + buf = (char*)my_malloc(PSI_INSTRUMENT_ME, MAX_BUF_SIZE, MYF(MY_WME)); + if (!buf) { + return; + } + + va_start(args, format); + vsprintf(buf, format, args); + va_end(args); + + mysql_mutex_lock(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + fprintf(ef, " Error in foreign key constraint of table %s:\n", + table_name); + fputs(buf, ef); + mysql_mutex_unlock(&dict_foreign_err_mutex); + + if (trx && trx->mysql_thd) { + THD* thd = (THD*)trx->mysql_thd; + + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + uint(convert_error_code_to_mysql(error, 0, thd)), buf); + } + + my_free(buf); +} + +/********************************************************************//** +Helper function to push frm mismatch error to error log and +if needed to sql-layer. */ +void +ib_push_frm_error( + THD* thd, /*!< in: MySQL thd */ + dict_table_t* ib_table, /*!< in: InnoDB table */ + TABLE* table, /*!< in: MySQL table */ + ulint n_keys, /*!< in: InnoDB #keys */ + bool push_warning) /*!< in: print warning ? */ +{ + switch (ib_table->dict_frm_mismatch) { + case DICT_FRM_NO_PK: + sql_print_error("Table %s has a primary key in " + "InnoDB data dictionary, but not " + "in MariaDB!" + " Have you mixed up " + ".frm files from different " + "installations? See " + "https://mariadb.com/kb/en/innodb-troubleshooting/\n", + ib_table->name.m_name); + + if (push_warning) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NO_SUCH_INDEX, + "InnoDB: Table %s has a " + "primary key in InnoDB data " + "dictionary, but not in " + "MariaDB!", ib_table->name.m_name); + } + break; + case DICT_NO_PK_FRM_HAS: + sql_print_error( + "Table %s has no primary key in InnoDB data " + "dictionary, but has one in MariaDB! If you " + "created the table with a MariaDB version < " + "3.23.54 and did not define a primary key, " + "but defined a unique key with all non-NULL " + "columns, then MariaDB internally treats that " + "key as the primary key. You can fix this " + "error by dump + DROP + CREATE + reimport " + "of the table.", ib_table->name.m_name); + + if (push_warning) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NO_SUCH_INDEX, + "InnoDB: Table %s has no " + "primary key in InnoDB data " + "dictionary, but has one in " + "MariaDB!", + ib_table->name.m_name); + } + break; + + case DICT_FRM_INCONSISTENT_KEYS: + sql_print_error("InnoDB: Table %s contains " ULINTPF " " + "indexes inside InnoDB, which " + "is different from the number of " + "indexes %u defined in the .frm file. See " + "https://mariadb.com/kb/en/innodb-troubleshooting/\n", + ib_table->name.m_name, n_keys, + table->s->keys); + + if (push_warning) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NO_SUCH_INDEX, + "InnoDB: Table %s contains " ULINTPF " " + "indexes inside InnoDB, which " + "is different from the number of " + "indexes %u defined in the MariaDB ", + ib_table->name.m_name, n_keys, + table->s->keys); + } + break; + + case DICT_FRM_CONSISTENT: + default: + sql_print_error("InnoDB: Table %s is consistent " + "on InnoDB data dictionary and MariaDB " + " FRM file.", + ib_table->name.m_name); + ut_error; + break; + } +} + +/** Writes 8 bytes to nth tuple field +@param[in] tuple where to write +@param[in] nth index in tuple +@param[in] data what to write +@param[in] buf field data buffer */ +static void set_tuple_col_8(dtuple_t *tuple, int col, uint64_t data, byte *buf) +{ + dfield_t *dfield= dtuple_get_nth_field(tuple, col); + ut_ad(dfield->type.len == 8); + if (dfield->len == UNIV_SQL_NULL) + { + dfield_set_data(dfield, buf, 8); + } + ut_ad(dfield->len == dfield->type.len && dfield->data); + mach_write_to_8(dfield->data, data); +} + +void ins_node_t::vers_update_end(row_prebuilt_t *prebuilt, bool history_row) +{ + ut_ad(prebuilt->ins_node == this); + trx_t *trx= prebuilt->trx; +#ifndef DBUG_OFF + ut_ad(table->vers_start != table->vers_end); + const mysql_row_templ_t *t= prebuilt->get_template_by_col(table->vers_end); + ut_ad(t); + ut_ad(t->mysql_col_len == 8); +#endif + + if (history_row) + { + set_tuple_col_8(row, table->vers_end, trx->id, vers_end_buf); + } + else /* ROW_INS_VERSIONED */ + { + set_tuple_col_8(row, table->vers_end, TRX_ID_MAX, vers_end_buf); +#ifndef DBUG_OFF + t= prebuilt->get_template_by_col(table->vers_start); + ut_ad(t); + ut_ad(t->mysql_col_len == 8); +#endif + set_tuple_col_8(row, table->vers_start, trx->id, vers_start_buf); + } + dict_index_t *clust_index= dict_table_get_first_index(table); + THD *thd= trx->mysql_thd; + TABLE *mysql_table= prebuilt->m_mysql_table; + mem_heap_t *local_heap= NULL; + for (ulint col_no= 0; col_no < dict_table_get_n_v_cols(table); col_no++) + { + const dict_v_col_t *v_col= dict_table_get_nth_v_col(table, col_no); + for (ulint i= 0; i < unsigned(v_col->num_base); i++) + if (v_col->base_col[i]->ind == table->vers_end) + innobase_get_computed_value(row, v_col, clust_index, &local_heap, + table->heap, NULL, thd, mysql_table, + mysql_table->record[0], NULL, NULL); + } + if (UNIV_LIKELY_NULL(local_heap)) + mem_heap_free(local_heap); +} + +/** Calculate aligned buffer pool size based on srv_buf_pool_chunk_unit, +if needed. +@param[in] size size in bytes +@return aligned size */ +ulint +buf_pool_size_align( + ulint size) +{ + const size_t m = srv_buf_pool_chunk_unit; + size = ut_max(size, (size_t) MYSQL_SYSVAR_NAME(buffer_pool_size).min_val); + + if (size % m == 0) { + return(size); + } else { + return (size / m + 1) * m; + } +} diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h new file mode 100644 index 00000000..1f42bf18 --- /dev/null +++ b/storage/innobase/handler/ha_innodb.h @@ -0,0 +1,937 @@ +/***************************************************************************** + +Copyright (c) 2000, 2017, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2013, 2022, MariaDB Corporation. + +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 Street, Fifth Floor, Boston, MA 02110-1335 USA + +*****************************************************************************/ +#ifdef WITH_WSREP +#include "wsrep_api.h" +#include <mysql/service_wsrep.h> +#endif /* WITH_WSREP */ + +#include "table.h" + +/* The InnoDB handler: the interface between MySQL and InnoDB. */ + +/** "GEN_CLUST_INDEX" is the name reserved for InnoDB default +system clustered index when there is no primary key. */ +extern const char innobase_index_reserve_name[]; + +/** Prebuilt structures in an InnoDB table handle used within MySQL */ +struct row_prebuilt_t; + +/** InnoDB transaction */ +struct trx_t; + +/** Engine specific table options are defined using this struct */ +struct ha_table_option_struct +{ + bool page_compressed; /*!< Table is using page compression + if this option is true. */ + ulonglong page_compression_level; /*!< Table page compression level + 0-9. */ + uint atomic_writes; /*!< Use atomic writes for this + table if this options is ON or + in DEFAULT if + innodb_use_atomic_writes. + Atomic writes are not used if + value OFF.*/ + uint encryption; /*!< DEFAULT, ON, OFF */ + ulonglong encryption_key_id; /*!< encryption key id */ +}; + +/** The class defining a handle to an Innodb table */ +class ha_innobase final : public handler +{ +public: + ha_innobase(handlerton* hton, TABLE_SHARE* table_arg); + ~ha_innobase() override; + + /** @return the transaction that last modified the table definition + @see dict_table_t::def_trx_id */ + ulonglong table_version() const override; + + /** Get the row type from the storage engine. If this method returns + ROW_TYPE_NOT_USED, the information in HA_CREATE_INFO should be used. */ + enum row_type get_row_type() const override; + + const char* table_type() const override; + + const char* index_type(uint key_number) override; + + Table_flags table_flags() const override; + + ulong index_flags(uint idx, uint part, bool all_parts) const override; + + uint max_supported_keys() const override; + + uint max_supported_key_length() const override; + + uint max_supported_key_part_length() const override; + + const key_map* keys_to_use_for_scanning() override; + + void column_bitmaps_signal() override; + + /** Opens dictionary table object using table name. For partition, we need to + try alternative lower/upper case names to support moving data files across + platforms. + @param[in] table_name name of the table/partition + @param[in] norm_name normalized name of the table/partition + @param[in] is_partition if this is a partition of a table + @param[in] ignore_err error to ignore for loading dictionary object + @return dictionary table object or NULL if not found */ + static dict_table_t* open_dict_table( + const char* table_name, + const char* norm_name, + bool is_partition, + dict_err_ignore_t ignore_err); + + int open(const char *name, int mode, uint test_if_locked) override; + + handler* clone(const char *name, MEM_ROOT *mem_root) override; + + int close(void) override; + + double scan_time() override; + + double read_time(uint index, uint ranges, ha_rows rows) override; + + int write_row(const uchar * buf) override; + + int update_row(const uchar * old_data, const uchar * new_data) override; + + int delete_row(const uchar * buf) override; + + bool was_semi_consistent_read() override; + + void try_semi_consistent_read(bool yes) override; + + void unlock_row() override; + + int index_init(uint index, bool sorted) override; + + int index_end() override; + + int index_read( + uchar* buf, + const uchar* key, + uint key_len, + ha_rkey_function find_flag) override; + + int index_read_last(uchar * buf, const uchar * key, + uint key_len) override; + + int index_next(uchar * buf) override; + + int index_next_same(uchar * buf, const uchar * key, + uint keylen) override; + + int index_prev(uchar * buf) override; + + int index_first(uchar * buf) override; + + int index_last(uchar * buf) override; + + /* Copy a cached MySQL row. If requested, also avoids + overwriting non-read columns. */ + void copy_cached_row(uchar *to_rec, const uchar *from_rec, + uint rec_length); + int rnd_init(bool scan) override; + + int rnd_end() override; + + int rnd_next(uchar *buf) override; + + int rnd_pos(uchar * buf, uchar *pos) override; + + int ft_init() override; + void ft_end() override { rnd_end(); } + FT_INFO *ft_init_ext(uint flags, uint inx, String* key) override; + int ft_read(uchar* buf) override; + + void position(const uchar *record) override; + + int info(uint) override; + + int analyze(THD* thd,HA_CHECK_OPT* check_opt) override; + + int optimize(THD* thd,HA_CHECK_OPT* check_opt) override; + + int discard_or_import_tablespace(my_bool discard) override; + + int extra(ha_extra_function operation) override; + + int reset() override; + + int external_lock(THD *thd, int lock_type) override; + + int start_stmt(THD *thd, thr_lock_type lock_type) override; + + ha_rows records_in_range( + uint inx, + const key_range* min_key, + const key_range* max_key, + page_range* pages) override; + + ha_rows estimate_rows_upper_bound() override; + + void update_create_info(HA_CREATE_INFO* create_info) override; + + int create( + const char* name, + TABLE* form, + HA_CREATE_INFO* create_info, + bool file_per_table, + trx_t* trx); + + int create( + const char* name, + TABLE* form, + HA_CREATE_INFO* create_info) override; + + int truncate() override; + + int delete_table(const char *name) override; + + int rename_table(const char* from, const char* to) override; + inline int defragment_table(); + int check(THD* thd, HA_CHECK_OPT* check_opt) override; + + inline void reload_statistics(); + + char* get_foreign_key_create_info() override; + + int get_foreign_key_list(THD *thd, + List<FOREIGN_KEY_INFO> *f_key_list) override; + + int get_parent_foreign_key_list( + THD* thd, + List<FOREIGN_KEY_INFO>* f_key_list) override; + + bool can_switch_engines() override; + + uint referenced_by_foreign_key() override; + + void free_foreign_key_create_info(char* str) override { my_free(str); } + + uint lock_count(void) const override; + + THR_LOCK_DATA** store_lock( + THD* thd, + THR_LOCK_DATA** to, + thr_lock_type lock_type) override; + + void init_table_handle_for_HANDLER() override; + + void get_auto_increment( + ulonglong offset, + ulonglong increment, + ulonglong nb_desired_values, + ulonglong* first_value, + ulonglong* nb_reserved_values) override; + + bool get_error_message(int error, String *buf) override; + + bool get_foreign_dup_key(char*, uint, char*, uint) override; + + uint8 table_cache_type() override; + + /** + Ask handler about permission to cache table during query registration + */ + my_bool register_query_cache_table( + THD* thd, + const char* table_key, + uint key_length, + qc_engine_callback* call_back, + ulonglong* engine_data) override; + + int cmp_ref(const uchar* ref1, const uchar* ref2) override; + + /** On-line ALTER TABLE interface @see handler0alter.cc @{ */ + + /** Check if InnoDB supports a particular alter table in-place + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used during in-place alter. + + @retval HA_ALTER_INPLACE_NOT_SUPPORTED Not supported + @retval HA_ALTER_INPLACE_INSTANT + MDL_EXCLUSIVE is needed for executing prepare_inplace_alter_table() + and commit_inplace_alter_table(). inplace_alter_table() + will not be called. + @retval HA_ALTER_INPLACE_COPY_NO_LOCK + MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded + to LOCK=NONE for rebuilding the table in inplace_alter_table() + @retval HA_ALTER_INPLACE_COPY_LOCK + MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded + to LOCK=SHARED for rebuilding the table in inplace_alter_table() + @retval HA_ALTER_INPLACE_NOCOPY_NO_LOCK + MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded + to LOCK=NONE for inplace_alter_table() which will not rebuild the table + @retval HA_ALTER_INPLACE_NOCOPY_LOCK + MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded + to LOCK=SHARED for inplace_alter_table() which will not rebuild + the table. */ + + enum_alter_inplace_result check_if_supported_inplace_alter( + TABLE* altered_table, + Alter_inplace_info* ha_alter_info) override; + + /** Allows InnoDB to update internal structures with concurrent + writes blocked (provided that check_if_supported_inplace_alter() + did not return HA_ALTER_INPLACE_NO_LOCK). + This will be invoked before inplace_alter_table(). + + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used during in-place alter. + + @retval true Failure + @retval false Success + */ + bool prepare_inplace_alter_table( + TABLE* altered_table, + Alter_inplace_info* ha_alter_info) override; + + /** Alter the table structure in-place with operations + specified using HA_ALTER_FLAGS and Alter_inplace_information. + The level of concurrency allowed during this operation depends + on the return value from check_if_supported_inplace_alter(). + + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used during in-place alter. + + @retval true Failure + @retval false Success + */ + bool inplace_alter_table( + TABLE* altered_table, + Alter_inplace_info* ha_alter_info) override; + + /** Commit or rollback the changes made during + prepare_inplace_alter_table() and inplace_alter_table() inside + the storage engine. Note that the allowed level of concurrency + during this operation will be the same as for + inplace_alter_table() and thus might be higher than during + prepare_inplace_alter_table(). (E.g concurrent writes were + blocked during prepare, but might not be during commit). + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used during in-place alter. + @param commit true => Commit, false => Rollback. + @retval true Failure + @retval false Success + */ + bool commit_inplace_alter_table( + TABLE* altered_table, + Alter_inplace_info* ha_alter_info, + bool commit) override; + /** @} */ + + bool check_if_incompatible_data( + HA_CREATE_INFO* info, + uint table_changes) override; + + /** @name Multi Range Read interface @{ */ + + /** Initialize multi range read @see DsMrr_impl::dsmrr_init + @param seq + @param seq_init_param + @param n_ranges + @param mode + @param buf */ + int multi_range_read_init( + RANGE_SEQ_IF* seq, + void* seq_init_param, + uint n_ranges, + uint mode, + HANDLER_BUFFER* buf) override; + + /** Process next multi range read @see DsMrr_impl::dsmrr_next + @param range_info */ + int multi_range_read_next(range_id_t *range_info) override; + + /** Initialize multi range read and get information. + @see ha_myisam::multi_range_read_info_const + @see DsMrr_impl::dsmrr_info_const + @param keyno + @param seq + @param seq_init_param + @param n_ranges + @param bufsz + @param flags + @param cost */ + ha_rows multi_range_read_info_const( + uint keyno, + RANGE_SEQ_IF* seq, + void* seq_init_param, + uint n_ranges, + uint* bufsz, + uint* flags, + Cost_estimate* cost) override; + + /** Initialize multi range read and get information. + @see DsMrr_impl::dsmrr_info + @param keyno + @param seq + @param seq_init_param + @param n_ranges + @param bufsz + @param flags + @param cost */ + ha_rows multi_range_read_info(uint keyno, uint n_ranges, uint keys, + uint key_parts, uint* bufsz, uint* flags, + Cost_estimate* cost) override; + + int multi_range_read_explain_info(uint mrr_mode, + char *str, size_t size) override; + + /** Attempt to push down an index condition. + @param[in] keyno MySQL key number + @param[in] idx_cond Index condition to be checked + @return idx_cond if pushed; NULL if not pushed */ + Item* idx_cond_push(uint keyno, Item* idx_cond) override; + /* @} */ + + /** Check if InnoDB is not storing virtual column metadata for a table. + @param s table definition (based on .frm file) + @return whether InnoDB will omit virtual column metadata */ + static bool omits_virtual_cols(const TABLE_SHARE& s) + { + return s.frm_version<FRM_VER_EXPRESSSIONS && s.virtual_fields; + } + + /** Push a primary key filter. + @param[in] pk_filter filter against which primary keys + are to be checked + @retval false if pushed (always) */ + bool rowid_filter_push(Rowid_filter *rowid_filter) override; + + bool can_convert_nocopy(const Field &field, + const Column_definition& new_field) const + override; + + /** @return whether innodb_strict_mode is active */ + static bool is_innodb_strict_mode(THD* thd); + + /** @return whether innodb_strict_mode is active */ + bool is_innodb_strict_mode() + { return is_innodb_strict_mode(m_user_thd); } + Compare_keys + compare_key_parts(const Field& old_field, + const Column_definition& new_field, + const KEY_PART_INFO& old_part, + const KEY_PART_INFO& new_part) const override; + +protected: + bool + can_convert_string(const Field_string* field, + const Column_definition& new_field) const; + bool can_convert_varstring( + const Field_varstring* field, + const Column_definition& new_field) const; + bool + can_convert_blob(const Field_blob* field, + const Column_definition& new_field) const; + + dberr_t innobase_get_autoinc(ulonglong* value); + dberr_t innobase_lock_autoinc(); + ulonglong innobase_peek_autoinc(); + dberr_t innobase_set_max_autoinc(ulonglong auto_inc); + + /** Resets a query execution 'template'. + @see build_template() */ + void reset_template(); + + /** @return whether the table is read-only */ + bool is_read_only(bool altering_to_supported= false) const; + + inline void update_thd(THD* thd); + void update_thd(); + + int general_fetch(uchar* buf, uint direction, uint match_mode); + int change_active_index(uint keynr); + /* @return true if it's necessary to switch current statement log + format from STATEMENT to ROW if binary log format is MIXED and + autoincrement values are changed in the statement */ + bool autoinc_lock_mode_stmt_unsafe() const override; + dict_index_t* innobase_get_index(uint keynr); + +#ifdef WITH_WSREP + int wsrep_append_keys( + THD *thd, + Wsrep_service_key_type key_type, + const uchar* record0, + const uchar* record1); +#endif + /** Builds a 'template' to the prebuilt struct. + + The template is used in fast retrieval of just those column + values MySQL needs in its processing. + @param whole_row true if access is needed to a whole row, + false if accessing individual fields is enough */ + void build_template(bool whole_row); + + int info_low(uint, bool); + + /** The multi range read session object */ + DsMrr_impl m_ds_mrr; + + /** Save CPU time with prebuilt/cached data structures */ + row_prebuilt_t* m_prebuilt; + + /** Thread handle of the user currently using the handler; + this is set in external_lock function */ + THD* m_user_thd; + + /** buffer used in updates */ + uchar* m_upd_buf; + + /** the size of upd_buf in bytes */ + ulint m_upd_buf_size; + + /** Flags that specificy the handler instance (table) capability. */ + Table_flags m_int_table_flags; + + /** Index into the server's primkary keye meta-data table->key_info{} */ + uint m_primary_key; + + /** this is set to 1 when we are starting a table scan but have + not yet fetched any row, else false */ + bool m_start_of_scan; + + /*!< match mode of the latest search: ROW_SEL_EXACT, + ROW_SEL_EXACT_PREFIX, or undefined */ + uint m_last_match_mode; + + /** If mysql has locked with external_lock() */ + bool m_mysql_has_locked; +}; + + +/* Some accessor functions which the InnoDB plugin needs, but which +can not be added to mysql/plugin.h as part of the public interface; +the definitions are bracketed with #ifdef INNODB_COMPATIBILITY_HOOKS */ + +#ifndef INNODB_COMPATIBILITY_HOOKS +#error InnoDB needs MySQL to be built with #define INNODB_COMPATIBILITY_HOOKS +#endif + +extern "C" { + +/** Check if a user thread is running a non-transactional update +@param thd user thread +@retval 0 the user thread is not running a non-transactional update +@retval 1 the user thread is running a non-transactional update */ +int thd_non_transactional_update(const MYSQL_THD thd); + +/** Get the user thread's binary logging format +@param thd user thread +@return Value to be used as index into the binlog_format_names array */ +int thd_binlog_format(const MYSQL_THD thd); + +/** Check if binary logging is filtered for thread's current db. +@param thd Thread handle +@retval 1 the query is not filtered, 0 otherwise. */ +bool thd_binlog_filter_ok(const MYSQL_THD thd); + +/** Check if the query may generate row changes which may end up in the binary. +@param thd Thread handle +@retval 1 the query may generate row changes, 0 otherwise. +*/ +bool thd_sqlcom_can_generate_row_events(const MYSQL_THD thd); + +/** Is strict sql_mode set. +@param thd Thread object +@return True if sql_mode has strict mode (all or trans), false otherwise. */ +bool thd_is_strict_mode(const MYSQL_THD thd); + +} /* extern "C" */ + +/** Get the file name and position of the MySQL binlog corresponding to the + * current commit. + */ +extern void mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file); + +struct trx_t; + +extern const struct _ft_vft ft_vft_result; + +/** Structure Returned by ha_innobase::ft_init_ext() */ +typedef struct new_ft_info +{ + struct _ft_vft *please; + struct _ft_vft_ext *could_you; + row_prebuilt_t* ft_prebuilt; + fts_result_t* ft_result; +} NEW_FT_INFO; + +/** +Allocates an InnoDB transaction for a MySQL handler object. +@return InnoDB transaction handle */ +trx_t* +innobase_trx_allocate( + MYSQL_THD thd); /*!< in: user thread handle */ + +/*********************************************************************//** +This function checks each index name for a table against reserved +system default primary index name 'GEN_CLUST_INDEX'. If a name +matches, this function pushes an warning message to the client, +and returns true. +@return true if the index name matches the reserved name */ +bool +innobase_index_name_is_reserved( + THD* thd, /*!< in/out: MySQL connection */ + const KEY* key_info, /*!< in: Indexes to be created */ + ulint num_of_keys) /*!< in: Number of indexes to + be created. */ + MY_ATTRIBUTE((nonnull(1), warn_unused_result)); + +/** Parse hint for table and its indexes, and update the information +in dictionary. +@param[in] thd Connection thread +@param[in,out] table Target table +@param[in] table_share Table definition */ +void +innobase_parse_hint_from_comment( + THD* thd, + dict_table_t* table, + const TABLE_SHARE* table_share); + +/** Class for handling create table information. */ +class create_table_info_t +{ +public: + /** Constructor. + Used in two ways: + - all but file_per_table is used, when creating the table. + - all but name/path is used, when validating options and using flags. */ + create_table_info_t( + THD* thd, + const TABLE* form, + HA_CREATE_INFO* create_info, + char* table_name, + char* remote_path, + bool file_per_table, + trx_t* trx = NULL); + + /** Initialize the object. */ + int initialize(); + + /** Set m_tablespace_type. */ + void set_tablespace_type(bool table_being_altered_is_file_per_table); + + /** Create InnoDB foreign keys from MySQL alter_info. */ + dberr_t create_foreign_keys(); + + /** Create the internal innodb table. + @param create_fk whether to add FOREIGN KEY constraints */ + int create_table(bool create_fk = true); + + static void create_table_update_dict(dict_table_t* table, THD* thd, + const HA_CREATE_INFO& info, + const TABLE& t); + + /** Validates the create options. Checks that the options + KEY_BLOCK_SIZE, ROW_FORMAT, DATA DIRECTORY, TEMPORARY & TABLESPACE + are compatible with each other and other settings. + These CREATE OPTIONS are not validated here unless innodb_strict_mode + is on. With strict mode, this function will report each problem it + finds using a custom message with error code + ER_ILLEGAL_HA_CREATE_OPTION, not its built-in message. + @return NULL if valid, string name of bad option if not. */ + const char* create_options_are_invalid(); + + bool gcols_in_fulltext_or_spatial(); + + /** Validates engine specific table options not handled by + SQL-parser. + @return NULL if valid, string name of bad option if not. */ + const char* check_table_options(); + + /** Validate DATA DIRECTORY option. */ + bool create_option_data_directory_is_valid(); + + /** Validate TABLESPACE option. */ + bool create_option_tablespace_is_valid(); + + /** Prepare to create a table. */ + int prepare_create_table(const char* name, bool strict = true); + + void allocate_trx(); + + /** Checks that every index have sane size. Depends on strict mode */ + bool row_size_is_acceptable(const dict_table_t& table, + bool strict) const; + /** Checks that given index have sane size. Depends on strict mode */ + bool row_size_is_acceptable(const dict_index_t& index, + bool strict) const; + + /** Determines InnoDB table flags. + If strict_mode=OFF, this will adjust the flags to what should be assumed. + @retval true if successful, false if error */ + bool innobase_table_flags(); + + /** Set flags and append '/' to remote path if necessary. */ + void set_remote_path_flags(); + + /** Get table flags. */ + ulint flags() const + { return(m_flags); } + + /** Update table flags. */ + void flags_set(ulint flags) { m_flags |= flags; } + + /** Get table flags2. */ + ulint flags2() const + { return(m_flags2); } + + /** Get trx. */ + trx_t* trx() const + { return(m_trx); } + + /** @return table name */ + const char* table_name() const { return(m_table_name); } + + /** @return the created table */ + dict_table_t *table() const { return m_table; } + + THD* thd() const { return(m_thd); } + +private: + /** Parses the table name into normal name and either temp path or + remote path if needed.*/ + int + parse_table_name( + const char* name); + + /** Create the internal innodb table definition. */ + int create_table_def(); + + /** Connection thread handle. */ + THD* m_thd; + + /** InnoDB transaction handle. */ + trx_t* m_trx; + + /** Information on table columns and indexes. */ + const TABLE* m_form; + + /** Value of innodb_default_row_format */ + const ulong m_default_row_format; + + /** Create options. */ + HA_CREATE_INFO* m_create_info; + + /** Table name */ + char* m_table_name; + /** Table */ + dict_table_t* m_table; + + /** Remote path (DATA DIRECTORY) or zero length-string */ + char* m_remote_path; + + /** Local copy of srv_file_per_table. */ + bool m_innodb_file_per_table; + + /** Allow file_per_table for this table either because: + 1) the setting innodb_file_per_table=on, + 2) it was explicitly requested by tablespace=innodb_file_per_table. + 3) the table being altered is currently file_per_table */ + bool m_allow_file_per_table; + + /** After all considerations, this shows whether we will actually + create a table and tablespace using file-per-table. */ + bool m_use_file_per_table; + + /** Using DATA DIRECTORY */ + bool m_use_data_dir; + + /** Table flags */ + ulint m_flags; + + /** Table flags2 */ + ulint m_flags2; +}; + +/** +Initialize the table FTS stopword list +@return TRUE if success */ +ibool +innobase_fts_load_stopword( +/*=======================*/ + dict_table_t* table, /*!< in: Table has the FTS */ + trx_t* trx, /*!< in: transaction */ + THD* thd) /*!< in: current thread */ + MY_ATTRIBUTE((warn_unused_result)); + +/** Some defines for innobase_fts_check_doc_id_index() return value */ +enum fts_doc_id_index_enum { + FTS_INCORRECT_DOC_ID_INDEX, + FTS_EXIST_DOC_ID_INDEX, + FTS_NOT_EXIST_DOC_ID_INDEX +}; + +/** +Check whether the table has a unique index with FTS_DOC_ID_INDEX_NAME +on the Doc ID column. +@return the status of the FTS_DOC_ID index */ +fts_doc_id_index_enum +innobase_fts_check_doc_id_index( + const dict_table_t* table, /*!< in: table definition */ + const TABLE* altered_table, /*!< in: MySQL table + that is being altered */ + ulint* fts_doc_col_no) /*!< out: The column number for + Doc ID */ + MY_ATTRIBUTE((warn_unused_result)); + +/** +Check whether the table has a unique index with FTS_DOC_ID_INDEX_NAME +on the Doc ID column in MySQL create index definition. +@return FTS_EXIST_DOC_ID_INDEX if there exists the FTS_DOC_ID index, +FTS_INCORRECT_DOC_ID_INDEX if the FTS_DOC_ID index is of wrong format */ +fts_doc_id_index_enum +innobase_fts_check_doc_id_index_in_def( + ulint n_key, /*!< in: Number of keys */ + const KEY* key_info) /*!< in: Key definitions */ + MY_ATTRIBUTE((warn_unused_result)); + +/** +Copy table flags from MySQL's TABLE_SHARE into an InnoDB table object. +Those flags are stored in .frm file and end up in the MySQL table object, +but are frequently used inside InnoDB so we keep their copies into the +InnoDB table object. */ +void +innobase_copy_frm_flags_from_table_share( + dict_table_t* innodb_table, /*!< in/out: InnoDB table */ + const TABLE_SHARE* table_share); /*!< in: table share */ + +/** Set up base columns for virtual column +@param[in] table the InnoDB table +@param[in] field MySQL field +@param[in,out] v_col virtual column to be set up */ +void +innodb_base_col_setup( + dict_table_t* table, + const Field* field, + dict_v_col_t* v_col); + +/** Set up base columns for stored column +@param[in] table InnoDB table +@param[in] field MySQL field +@param[in,out] s_col stored column */ +void +innodb_base_col_setup_for_stored( + const dict_table_t* table, + const Field* field, + dict_s_col_t* s_col); + +/** whether this is a stored generated column */ +#define innobase_is_s_fld(field) ((field)->vcol_info && (field)->stored_in_db()) + +/** Converts a search mode flag understood by MySQL to a flag understood +by InnoDB. +@param[in] find_flag MySQL search mode flag. +@return InnoDB search mode flag. */ +page_cur_mode_t +convert_search_mode_to_innobase( + enum ha_rkey_function find_flag); + +/** Commits a transaction in an InnoDB database. +@param[in] trx Transaction handle. */ +void +innobase_commit_low( + trx_t* trx); + +extern my_bool innobase_stats_on_metadata; + +/** Calculate Record Per Key value. +Need to exclude the NULL value if innodb_stats_method is set to "nulls_ignored" +@param[in] index InnoDB index. +@param[in] i The column we are calculating rec per key. +@param[in] records Estimated total records. +@return estimated record per key value */ +/* JAN: TODO: MySQL 5.7 */ +typedef float rec_per_key_t; +rec_per_key_t +innodb_rec_per_key( + dict_index_t* index, + ulint i, + ha_rows records); + +/** Build template for the virtual columns and their base columns +@param[in] table MySQL TABLE +@param[in] ib_table InnoDB dict_table_t +@param[in,out] s_templ InnoDB template structure +@param[in] add_v new virtual columns added along with + add index call +@param[in] locked true if innobase_share_mutex is held */ +void +innobase_build_v_templ( + const TABLE* table, + const dict_table_t* ib_table, + dict_vcol_templ_t* s_templ, + const dict_add_v_col_t* add_v, + bool locked); + +/** callback used by MySQL server layer to initialized +the table virtual columns' template +@param[in] table MySQL TABLE +@param[in,out] ib_table InnoDB dict_table_t */ +void +innobase_build_v_templ_callback( + const TABLE* table, + void* ib_table); + +/** Callback function definition, used by MySQL server layer to initialized +the table virtual columns' template */ +typedef void (*my_gcolumn_templatecallback_t)(const TABLE*, void*); + +/** Convert MySQL column number to dict_table_t::cols[] offset. +@param[in] field non-virtual column +@return column number relative to dict_table_t::cols[] */ +unsigned +innodb_col_no(const Field* field) + MY_ATTRIBUTE((nonnull, warn_unused_result)); + +/********************************************************************//** +Helper function to push frm mismatch error to error log and +if needed to sql-layer. */ +void +ib_push_frm_error( + THD* thd, /*!< in: MySQL thd */ + dict_table_t* ib_table, /*!< in: InnoDB table */ + TABLE* table, /*!< in: MySQL table */ + ulint n_keys, /*!< in: InnoDB #keys */ + bool push_warning); /*!< in: print warning ? */ + +/** Check each index part length whether they not exceed the max limit +@param[in] max_field_len maximum allowed key part length +@param[in] key MariaDB key definition +@return true if index column length exceeds limit */ +MY_ATTRIBUTE((warn_unused_result)) +bool too_big_key_part_length(size_t max_field_len, const KEY& key); + +/** This function is used to rollback one X/Open XA distributed transaction +which is in the prepared state + +@param[in] hton InnoDB handlerton +@param[in] xid X/Open XA transaction identification + +@return 0 or error number */ +int innobase_rollback_by_xid(handlerton* hton, XID* xid); diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc new file mode 100644 index 00000000..40370ac5 --- /dev/null +++ b/storage/innobase/handler/handler0alter.cc @@ -0,0 +1,11843 @@ +/***************************************************************************** + +Copyright (c) 2005, 2019, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2013, 2023, MariaDB Corporation. + +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 Street, Fifth Floor, Boston, MA 02110-1335 USA + +*****************************************************************************/ + +/**************************************************//** +@file handler/handler0alter.cc +Smart ALTER TABLE +*******************************************************/ + +/* Include necessary SQL headers */ +#include "univ.i" +#include <debug_sync.h> +#include <log.h> +#include <sql_lex.h> +#include <sql_class.h> +#include <sql_table.h> +#include <mysql/plugin.h> + +/* Include necessary InnoDB headers */ +#include "btr0sea.h" +#include "dict0crea.h" +#include "dict0dict.h" +#include "dict0load.h" +#include "dict0stats.h" +#include "dict0stats_bg.h" +#include "log0log.h" +#include "rem0types.h" +#include "row0log.h" +#include "row0merge.h" +#include "row0ins.h" +#include "row0row.h" +#include "row0upd.h" +#include "trx0trx.h" +#include "trx0purge.h" +#include "handler0alter.h" +#include "srv0mon.h" +#include "srv0srv.h" +#include "fts0priv.h" +#include "fts0plugin.h" +#include "pars0pars.h" +#include "row0sel.h" +#include "ha_innodb.h" +#include "ut0stage.h" +#include <thread> +#include <sstream> + +/** File format constraint for ALTER TABLE */ +extern ulong innodb_instant_alter_column_allowed; + +static const char *MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN= + "INPLACE ADD or DROP of virtual columns cannot be " + "combined with other ALTER TABLE actions"; + +/** Operations for creating secondary indexes (no rebuild needed) */ +static const alter_table_operations INNOBASE_ONLINE_CREATE + = ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX + | ALTER_ADD_UNIQUE_INDEX; + +/** Operations that require filling in default values for columns */ +static const alter_table_operations INNOBASE_DEFAULTS + = ALTER_COLUMN_NOT_NULLABLE + | ALTER_ADD_STORED_BASE_COLUMN; + + +/** Operations that require knowledge about row_start, row_end values */ +static const alter_table_operations INNOBASE_ALTER_VERSIONED_REBUILD + = ALTER_ADD_SYSTEM_VERSIONING + | ALTER_DROP_SYSTEM_VERSIONING; + +/** Operations for rebuilding a table in place */ +static const alter_table_operations INNOBASE_ALTER_REBUILD + = ALTER_ADD_PK_INDEX + | ALTER_DROP_PK_INDEX + | ALTER_OPTIONS + /* ALTER_OPTIONS needs to check alter_options_need_rebuild() */ + | ALTER_COLUMN_NULLABLE + | INNOBASE_DEFAULTS + | ALTER_STORED_COLUMN_ORDER + | ALTER_DROP_STORED_COLUMN + | ALTER_RECREATE_TABLE + /* + | ALTER_STORED_COLUMN_TYPE + */ + | INNOBASE_ALTER_VERSIONED_REBUILD + ; + +/** Operations that require changes to data */ +static const alter_table_operations INNOBASE_ALTER_DATA + = INNOBASE_ONLINE_CREATE | INNOBASE_ALTER_REBUILD; + +/** Operations for altering a table that InnoDB does not care about */ +static const alter_table_operations INNOBASE_INPLACE_IGNORE + = ALTER_COLUMN_DEFAULT + | ALTER_PARTITIONED + | ALTER_COLUMN_COLUMN_FORMAT + | ALTER_COLUMN_STORAGE_TYPE + | ALTER_CONVERT_TO + | ALTER_VIRTUAL_GCOL_EXPR + | ALTER_DROP_CHECK_CONSTRAINT + | ALTER_RENAME + | ALTER_INDEX_ORDER + | ALTER_COLUMN_INDEX_LENGTH + | ALTER_CHANGE_INDEX_COMMENT + | ALTER_INDEX_IGNORABILITY; + +/** Operations on foreign key definitions (changing the schema only) */ +static const alter_table_operations INNOBASE_FOREIGN_OPERATIONS + = ALTER_DROP_FOREIGN_KEY + | ALTER_ADD_FOREIGN_KEY; + +/** Operations that InnoDB cares about and can perform without creating data */ +static const alter_table_operations INNOBASE_ALTER_NOCREATE + = ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX + | ALTER_DROP_UNIQUE_INDEX; + +/** Operations that InnoDB cares about and can perform without validation */ +static const alter_table_operations INNOBASE_ALTER_NOVALIDATE + = INNOBASE_ALTER_NOCREATE + | ALTER_VIRTUAL_COLUMN_ORDER + | ALTER_COLUMN_NAME + | INNOBASE_FOREIGN_OPERATIONS + | ALTER_COLUMN_UNVERSIONED + | ALTER_DROP_VIRTUAL_COLUMN; + +/** Operations that InnoDB cares about and can perform without rebuild */ +static const alter_table_operations INNOBASE_ALTER_NOREBUILD + = INNOBASE_ONLINE_CREATE + | INNOBASE_ALTER_NOCREATE; + +/** Operations that can be performed instantly, without inplace_alter_table() */ +static const alter_table_operations INNOBASE_ALTER_INSTANT + = ALTER_VIRTUAL_COLUMN_ORDER + | ALTER_COLUMN_NAME + | ALTER_ADD_VIRTUAL_COLUMN + | INNOBASE_FOREIGN_OPERATIONS + | ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE + | ALTER_COLUMN_UNVERSIONED + | ALTER_RENAME_INDEX + | ALTER_DROP_VIRTUAL_COLUMN; + +/** Initialize instant->field_map. +@param[in] table table definition to copy from */ +inline void dict_table_t::init_instant(const dict_table_t& table) +{ + const dict_index_t& oindex __attribute__((unused))= *table.indexes.start; + dict_index_t& index = *indexes.start; + const unsigned u = index.first_user_field(); + DBUG_ASSERT(u == oindex.first_user_field()); + DBUG_ASSERT(index.n_fields >= oindex.n_fields); + + field_map_element_t* field_map_it = static_cast<field_map_element_t*>( + mem_heap_zalloc(heap, (index.n_fields - u) + * sizeof *field_map_it)); + instant->field_map = field_map_it; + + ut_d(unsigned n_drop = 0); + ut_d(unsigned n_nullable = 0); + for (unsigned i = u; i < index.n_fields; i++) { + auto& f = index.fields[i]; + ut_d(n_nullable += f.col->is_nullable()); + + if (!f.col->is_dropped()) { + (*field_map_it++).set_ind(f.col->ind); + continue; + } + + auto fixed_len = dict_col_get_fixed_size( + f.col, not_redundant()); + field_map_it->set_dropped(); + if (!f.col->is_nullable()) { + field_map_it->set_not_null(); + } + field_map_it->set_ind(fixed_len + ? uint16_t(fixed_len + 1) + : DATA_BIG_COL(f.col)); + field_map_it++; + ut_ad(f.col >= table.instant->dropped); + ut_ad(f.col < table.instant->dropped + + table.instant->n_dropped); + ut_d(n_drop++); + size_t d = f.col - table.instant->dropped; + ut_ad(f.col == &table.instant->dropped[d]); + ut_ad(d <= instant->n_dropped); + f.col = &instant->dropped[d]; + } + ut_ad(n_drop == n_dropped()); + ut_ad(field_map_it == &instant->field_map[index.n_fields - u]); + ut_ad(index.n_nullable == n_nullable); +} + +/** Set is_instant() before instant_column(). +@param[in] old previous table definition +@param[in] col_map map from old.cols[] and old.v_cols[] to this +@param[out] first_alter_pos 0, or 1 + first changed column position */ +inline void dict_table_t::prepare_instant(const dict_table_t& old, + const ulint* col_map, + unsigned& first_alter_pos) +{ + DBUG_ASSERT(!is_instant()); + DBUG_ASSERT(n_dropped() == 0); + DBUG_ASSERT(old.n_cols == old.n_def); + DBUG_ASSERT(n_cols == n_def); + DBUG_ASSERT(old.supports_instant()); + DBUG_ASSERT(not_redundant() == old.not_redundant()); + DBUG_ASSERT(DICT_TF_HAS_ATOMIC_BLOBS(flags) + == DICT_TF_HAS_ATOMIC_BLOBS(old.flags)); + DBUG_ASSERT(!persistent_autoinc + || persistent_autoinc == old.persistent_autoinc); + /* supports_instant() does not necessarily hold here, + in case ROW_FORMAT=COMPRESSED according to the + MariaDB data dictionary, and ALTER_OPTIONS was not set. + If that is the case, the instant ALTER TABLE would keep + the InnoDB table in its current format. */ + + dict_index_t& oindex = *old.indexes.start; + dict_index_t& index = *indexes.start; + first_alter_pos = 0; + + for (unsigned i = 0; i + DATA_N_SYS_COLS < old.n_cols; i++) { + if (col_map[i] != i) { + first_alter_pos = 1 + i; + goto add_metadata; + } + } + + if (!old.instant) { + /* Columns were not dropped or reordered. + Therefore columns must have been added at the end, + or modified instantly in place. */ + DBUG_ASSERT(index.n_fields >= oindex.n_fields); + DBUG_ASSERT(index.n_fields > oindex.n_fields + || !not_redundant()); +#ifdef UNIV_DEBUG + if (index.n_fields == oindex.n_fields) { + ut_ad(!not_redundant()); + for (unsigned i = index.n_fields; i--; ) { + ut_ad(index.fields[i].col->same_format( + *oindex.fields[i].col)); + } + } +#endif +set_core_fields: + index.n_core_fields = oindex.n_core_fields; + index.n_core_null_bytes = oindex.n_core_null_bytes; + } else { +add_metadata: + const unsigned n_old_drop = old.n_dropped(); + unsigned n_drop = n_old_drop; + for (unsigned i = old.n_cols; i--; ) { + if (col_map[i] == ULINT_UNDEFINED) { + DBUG_ASSERT(i + DATA_N_SYS_COLS + < uint(old.n_cols)); + n_drop++; + } + } + + instant = new (mem_heap_alloc(heap, sizeof(dict_instant_t))) + dict_instant_t(); + instant->n_dropped = n_drop; + if (n_drop) { + instant->dropped + = static_cast<dict_col_t*>( + mem_heap_alloc(heap, n_drop + * sizeof(dict_col_t))); + if (n_old_drop) { + memcpy(instant->dropped, old.instant->dropped, + n_old_drop * sizeof(dict_col_t)); + } + } else { + instant->dropped = NULL; + } + + for (unsigned i = 0, d = n_old_drop; i < old.n_cols; i++) { + if (col_map[i] == ULINT_UNDEFINED) { + (new (&instant->dropped[d++]) + dict_col_t(old.cols[i]))->set_dropped(); + } + } +#ifndef DBUG_OFF + for (unsigned i = 0; i < n_drop; i++) { + DBUG_ASSERT(instant->dropped[i].is_dropped()); + } +#endif + const unsigned n_fields = index.n_fields + n_dropped(); + + DBUG_ASSERT(n_fields >= oindex.n_fields); + dict_field_t* fields = static_cast<dict_field_t*>( + mem_heap_zalloc(heap, n_fields * sizeof *fields)); + unsigned i = 0, j = 0, n_nullable = 0; + ut_d(uint core_null = 0); + for (; i < oindex.n_fields; i++) { + DBUG_ASSERT(j <= i); + dict_field_t&f = fields[i] = oindex.fields[i]; + if (f.col->is_dropped()) { + /* The column has been instantly + dropped earlier. */ + DBUG_ASSERT(f.col >= old.instant->dropped); + { + size_t d = f.col + - old.instant->dropped; + DBUG_ASSERT(d < n_old_drop); + DBUG_ASSERT(&old.instant->dropped[d] + == f.col); + DBUG_ASSERT(!f.name); + f.col = instant->dropped + d; + } + if (f.col->is_nullable()) { +found_nullable: + n_nullable++; + ut_d(core_null + += i < oindex.n_core_fields); + } + continue; + } + + const ulint col_ind = col_map[f.col->ind]; + if (col_ind != ULINT_UNDEFINED) { + if (index.fields[j].col->ind != col_ind) { + /* The fields for instantly + added columns must be placed + last in the clustered index. + Keep pre-existing fields in + the same position. */ + uint k; + for (k = j + 1; k < index.n_fields; + k++) { + if (index.fields[k].col->ind + == col_ind) { + goto found_j; + } + } + DBUG_ASSERT("no such col" == 0); +found_j: + std::swap(index.fields[j], + index.fields[k]); + } + DBUG_ASSERT(index.fields[j].col->ind + == col_ind); + fields[i] = index.fields[j++]; + DBUG_ASSERT(!fields[i].col->is_dropped()); + DBUG_ASSERT(fields[i].name + == fields[i].col->name(*this)); + if (fields[i].col->is_nullable()) { + goto found_nullable; + } + continue; + } + + /* This column is being dropped. */ + unsigned d = n_old_drop; + for (unsigned c = 0; c < f.col->ind; c++) { + d += col_map[c] == ULINT_UNDEFINED; + } + DBUG_ASSERT(d < n_drop); + f.col = &instant->dropped[d]; + f.name = NULL; + if (f.col->is_nullable()) { + goto found_nullable; + } + } + + /* In case of discarded tablespace, InnoDB can't + read the root page. So assign the null bytes based + on nullabled fields */ + if (!oindex.table->space) { + oindex.n_core_null_bytes = static_cast<uint8_t>( + UT_BITS_IN_BYTES(unsigned(oindex.n_nullable))); + } + + /* The n_core_null_bytes only matters for + ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC tables. */ + ut_ad(UT_BITS_IN_BYTES(core_null) == oindex.n_core_null_bytes + || !not_redundant()); + DBUG_ASSERT(i >= oindex.n_core_fields); + DBUG_ASSERT(j <= i); + DBUG_ASSERT(n_fields - (i - j) == index.n_fields); + std::sort(index.fields + j, index.fields + index.n_fields, + [](const dict_field_t& a, const dict_field_t& b) + { return a.col->ind < b.col->ind; }); + for (; i < n_fields; i++) { + fields[i] = index.fields[j++]; + n_nullable += fields[i].col->is_nullable(); + DBUG_ASSERT(!fields[i].col->is_dropped()); + DBUG_ASSERT(fields[i].name + == fields[i].col->name(*this)); + } + DBUG_ASSERT(j == index.n_fields); + index.n_fields = index.n_def = n_fields + & dict_index_t::MAX_N_FIELDS; + index.fields = fields; + DBUG_ASSERT(n_nullable >= index.n_nullable); + DBUG_ASSERT(n_nullable >= oindex.n_nullable); + index.n_nullable = n_nullable & dict_index_t::MAX_N_FIELDS; + goto set_core_fields; + } + + DBUG_ASSERT(n_cols + n_dropped() >= old.n_cols + old.n_dropped()); + DBUG_ASSERT(n_dropped() >= old.n_dropped()); + DBUG_ASSERT(index.n_core_fields == oindex.n_core_fields); + DBUG_ASSERT(index.n_core_null_bytes == oindex.n_core_null_bytes); +} + +/** Adjust index metadata for instant ADD/DROP/reorder COLUMN. +@param[in] clustered index definition after instant ALTER TABLE */ +inline void dict_index_t::instant_add_field(const dict_index_t& instant) +{ + DBUG_ASSERT(is_primary()); + DBUG_ASSERT(instant.is_primary()); + DBUG_ASSERT(!has_virtual()); + DBUG_ASSERT(!instant.has_virtual()); + DBUG_ASSERT(instant.n_core_fields <= instant.n_fields); + DBUG_ASSERT(n_def == n_fields); + DBUG_ASSERT(instant.n_def == instant.n_fields); + DBUG_ASSERT(type == instant.type); + DBUG_ASSERT(trx_id_offset == instant.trx_id_offset); + DBUG_ASSERT(n_user_defined_cols == instant.n_user_defined_cols); + DBUG_ASSERT(n_uniq == instant.n_uniq); + DBUG_ASSERT(instant.n_fields >= n_fields); + DBUG_ASSERT(instant.n_nullable >= n_nullable); + DBUG_ASSERT(instant.n_core_fields == n_core_fields); + DBUG_ASSERT(instant.n_core_null_bytes == n_core_null_bytes); + + /* instant will have all fields (including ones for columns + that have been or are being instantly dropped) in the same position + as this index. Fields for any added columns are appended at the end. */ +#ifndef DBUG_OFF + for (unsigned i = 0; i < n_fields; i++) { + DBUG_ASSERT(fields[i].same(instant.fields[i])); + DBUG_ASSERT(instant.fields[i].col->same_format(*fields[i] + .col)); + /* Instant conversion from NULL to NOT NULL is not allowed. */ + DBUG_ASSERT(!fields[i].col->is_nullable() + || instant.fields[i].col->is_nullable()); + DBUG_ASSERT(fields[i].col->is_nullable() + == instant.fields[i].col->is_nullable() + || !table->not_redundant()); + } +#endif + n_fields = instant.n_fields; + n_def = instant.n_def; + n_nullable = instant.n_nullable; + fields = static_cast<dict_field_t*>( + mem_heap_dup(heap, instant.fields, n_fields * sizeof *fields)); + + ut_d(unsigned n_null = 0); + ut_d(unsigned n_dropped = 0); + + for (unsigned i = 0; i < n_fields; i++) { + const dict_col_t* icol = instant.fields[i].col; + dict_field_t& f = fields[i]; + ut_d(n_null += icol->is_nullable()); + DBUG_ASSERT(!icol->is_virtual()); + if (icol->is_dropped()) { + ut_d(n_dropped++); + f.col->set_dropped(); + f.name = NULL; + } else { + f.col = &table->cols[icol - instant.table->cols]; + f.name = f.col->name(*table); + } + } + + ut_ad(n_null == n_nullable); + ut_ad(n_dropped == instant.table->n_dropped()); +} + +/** Adjust table metadata for instant ADD/DROP/reorder COLUMN. +@param[in] table altered table (with dropped columns) +@param[in] col_map mapping from cols[] and v_cols[] to table +@return whether the metadata record must be updated */ +inline bool dict_table_t::instant_column(const dict_table_t& table, + const ulint* col_map) +{ + DBUG_ASSERT(!table.cached); + DBUG_ASSERT(table.n_def == table.n_cols); + DBUG_ASSERT(table.n_t_def == table.n_t_cols); + DBUG_ASSERT(n_def == n_cols); + DBUG_ASSERT(n_t_def == n_t_cols); + DBUG_ASSERT(n_v_def == n_v_cols); + DBUG_ASSERT(table.n_v_def == table.n_v_cols); + DBUG_ASSERT(table.n_cols + table.n_dropped() >= n_cols + n_dropped()); + DBUG_ASSERT(!table.persistent_autoinc + || persistent_autoinc == table.persistent_autoinc); + ut_ad(dict_sys.locked()); + + { + const char* end = table.col_names; + for (unsigned i = table.n_cols; i--; ) end += strlen(end) + 1; + + col_names = static_cast<char*>( + mem_heap_dup(heap, table.col_names, + ulint(end - table.col_names))); + } + const dict_col_t* const old_cols = cols; + cols = static_cast<dict_col_t*>(mem_heap_dup(heap, table.cols, + table.n_cols + * sizeof *cols)); + + /* Preserve the default values of previously instantly added + columns, or copy the new default values to this->heap. */ + for (uint16_t i = 0; i < table.n_cols; i++) { + dict_col_t& c = cols[i]; + + if (const dict_col_t* o = find(old_cols, col_map, n_cols, i)) { + c.def_val = o->def_val; + DBUG_ASSERT(!((c.prtype ^ o->prtype) + & ~(DATA_NOT_NULL | DATA_VERSIONED + | CHAR_COLL_MASK << 16 + | DATA_LONG_TRUE_VARCHAR))); + DBUG_ASSERT(c.same_type(*o)); + DBUG_ASSERT(c.len >= o->len); + + if (o->vers_sys_start()) { + ut_ad(o->ind == vers_start); + vers_start = i & dict_index_t::MAX_N_FIELDS; + } else if (o->vers_sys_end()) { + ut_ad(o->ind == vers_end); + vers_end = i & dict_index_t::MAX_N_FIELDS; + } + continue; + } + + DBUG_ASSERT(c.is_added()); + if (c.def_val.len <= UNIV_PAGE_SIZE_MAX + && (!c.def_val.len + || !memcmp(c.def_val.data, field_ref_zero, + c.def_val.len))) { + c.def_val.data = field_ref_zero; + } else if (const void*& d = c.def_val.data) { + d = mem_heap_dup(heap, d, c.def_val.len); + } else { + DBUG_ASSERT(c.def_val.len == UNIV_SQL_NULL); + } + } + + n_t_def = (n_t_def + (table.n_cols - n_cols)) + & dict_index_t::MAX_N_FIELDS; + n_t_cols = (n_t_cols + (table.n_cols - n_cols)) + & dict_index_t::MAX_N_FIELDS; + n_def = table.n_cols; + + const dict_v_col_t* const old_v_cols = v_cols; + + if (const char* end = table.v_col_names) { + for (unsigned i = table.n_v_cols; i--; ) { + end += strlen(end) + 1; + } + + v_col_names = static_cast<char*>( + mem_heap_dup(heap, table.v_col_names, + ulint(end - table.v_col_names))); + v_cols = static_cast<dict_v_col_t*>( + mem_heap_alloc(heap, table.n_v_cols * sizeof(*v_cols))); + for (ulint i = table.n_v_cols; i--; ) { + new (&v_cols[i]) dict_v_col_t(table.v_cols[i]); + v_cols[i].v_indexes.clear(); + } + } else { + ut_ad(table.n_v_cols == 0); + v_col_names = NULL; + v_cols = NULL; + } + + n_t_def = (n_t_def + (table.n_v_cols - n_v_cols)) + & dict_index_t::MAX_N_FIELDS; + n_t_cols = (n_t_cols + (table.n_v_cols - n_v_cols)) + & dict_index_t::MAX_N_FIELDS; + n_v_def = table.n_v_cols; + + for (unsigned i = 0; i < n_v_def; i++) { + dict_v_col_t& v = v_cols[i]; + DBUG_ASSERT(v.v_indexes.empty()); + v.base_col = static_cast<dict_col_t**>( + mem_heap_dup(heap, v.base_col, + v.num_base * sizeof *v.base_col)); + + for (ulint n = v.num_base; n--; ) { + dict_col_t*& base = v.base_col[n]; + if (base->is_virtual()) { + } else if (base >= table.cols + && base < table.cols + table.n_cols) { + /* The base column was instantly added. */ + size_t c = base - table.cols; + DBUG_ASSERT(base == &table.cols[c]); + base = &cols[c]; + } else { + DBUG_ASSERT(base >= old_cols); + size_t c = base - old_cols; + DBUG_ASSERT(c + DATA_N_SYS_COLS < n_cols); + DBUG_ASSERT(base == &old_cols[c]); + DBUG_ASSERT(col_map[c] + DATA_N_SYS_COLS + < n_cols); + base = &cols[col_map[c]]; + } + } + } + + dict_index_t* index = dict_table_get_first_index(this); + bool metadata_changed; + { + const dict_index_t& i = *dict_table_get_first_index(&table); + metadata_changed = i.n_fields > index->n_fields; + ut_ad(i.n_fields >= index->n_fields); + index->instant_add_field(i); + } + + if (instant || table.instant) { + const auto old_instant = instant; + /* FIXME: add instant->heap, and transfer ownership here */ + if (!instant) { + instant = new (mem_heap_zalloc(heap, sizeof *instant)) + dict_instant_t(); + goto dup_dropped; + } else if (n_dropped() < table.n_dropped()) { +dup_dropped: + instant->dropped = static_cast<dict_col_t*>( + mem_heap_dup(heap, table.instant->dropped, + table.instant->n_dropped + * sizeof *instant->dropped)); + instant->n_dropped = table.instant->n_dropped; + } else if (table.instant->n_dropped) { + memcpy(instant->dropped, table.instant->dropped, + table.instant->n_dropped + * sizeof *instant->dropped); + } + + const field_map_element_t* field_map = old_instant + ? old_instant->field_map : NULL; + + init_instant(table); + + if (!metadata_changed) { + metadata_changed = !field_map + || memcmp(field_map, + instant->field_map, + (index->n_fields + - index->first_user_field()) + * sizeof *field_map); + } + } + + while ((index = dict_table_get_next_index(index)) != NULL) { + if (index->to_be_dropped) { + continue; + } + for (unsigned i = 0; i < index->n_fields; i++) { + dict_field_t& f = index->fields[i]; + if (f.col >= table.cols + && f.col < table.cols + table.n_cols) { + /* This is an instantly added column + in a newly added index. */ + DBUG_ASSERT(!f.col->is_virtual()); + size_t c = f.col - table.cols; + DBUG_ASSERT(f.col == &table.cols[c]); + f.col = &cols[c]; + } else if (f.col >= &table.v_cols->m_col + && f.col < &table.v_cols[n_v_cols].m_col) { + /* This is an instantly added virtual column + in a newly added index. */ + DBUG_ASSERT(f.col->is_virtual()); + size_t c = reinterpret_cast<dict_v_col_t*>( + f.col) - table.v_cols; + DBUG_ASSERT(f.col == &table.v_cols[c].m_col); + f.col = &v_cols[c].m_col; + } else if (f.col < old_cols + || f.col >= old_cols + n_cols) { + DBUG_ASSERT(f.col->is_virtual()); + f.col = &v_cols[col_map[ + reinterpret_cast<dict_v_col_t*>( + f.col) + - old_v_cols + n_cols]].m_col; + } else { + f.col = &cols[col_map[f.col - old_cols]]; + DBUG_ASSERT(!f.col->is_virtual()); + } + f.name = f.col->name(*this); + if (f.col->is_virtual()) { + dict_v_col_t* v_col = reinterpret_cast + <dict_v_col_t*>(f.col); + v_col->v_indexes.push_front( + dict_v_idx_t(index, i)); + } + } + } + + n_cols = table.n_cols; + n_v_cols = table.n_v_cols; + return metadata_changed; +} + +/** Find the old column number for the given new column position. +@param[in] col_map column map from old column to new column +@param[in] pos new column position +@param[in] n number of columns present in the column map +@return old column position for the given new column position. */ +static ulint find_old_col_no(const ulint* col_map, ulint pos, ulint n) +{ + do { + ut_ad(n); + } while (col_map[--n] != pos); + return n; +} + +/** Roll back instant_column(). +@param[in] old_n_cols original n_cols +@param[in] old_cols original cols +@param[in] old_col_names original col_names +@param[in] old_instant original instant structure +@param[in] old_fields original fields +@param[in] old_n_fields original number of fields +@param[in] old_n_core_fields original number of core fields +@param[in] old_n_v_cols original n_v_cols +@param[in] old_v_cols original v_cols +@param[in] old_v_col_names original v_col_names +@param[in] col_map column map */ +inline void dict_table_t::rollback_instant( + unsigned old_n_cols, + dict_col_t* old_cols, + const char* old_col_names, + dict_instant_t* old_instant, + dict_field_t* old_fields, + unsigned old_n_fields, + unsigned old_n_core_fields, + unsigned old_n_v_cols, + dict_v_col_t* old_v_cols, + const char* old_v_col_names, + const ulint* col_map) +{ + ut_ad(dict_sys.locked()); + + if (cols == old_cols) { + /* Alter fails before instant operation happens. + So there is no need to do rollback instant operation */ + return; + } + + dict_index_t* index = indexes.start; + /* index->is_instant() does not necessarily hold here, because + the table may have been emptied */ + DBUG_ASSERT(old_n_cols >= DATA_N_SYS_COLS); + DBUG_ASSERT(n_cols == n_def); + DBUG_ASSERT(index->n_def == index->n_fields); + DBUG_ASSERT(index->n_core_fields <= index->n_fields); + DBUG_ASSERT(old_n_core_fields <= old_n_fields); + DBUG_ASSERT(instant || !old_instant); + + instant = old_instant; + + index->n_nullable = 0; + + for (unsigned i = old_n_fields; i--; ) { + if (old_fields[i].col->is_nullable()) { + index->n_nullable++; + } + } + + for (unsigned i = n_v_cols; i--; ) { + v_cols[i].~dict_v_col_t(); + } + + index->n_core_fields = ((index->n_fields == index->n_core_fields) + ? old_n_fields + : old_n_core_fields) + & dict_index_t::MAX_N_FIELDS; + index->n_def = index->n_fields = old_n_fields + & dict_index_t::MAX_N_FIELDS; + index->n_core_null_bytes = static_cast<uint8_t>( + UT_BITS_IN_BYTES(index->get_n_nullable(index->n_core_fields))); + + const dict_col_t* const new_cols = cols; + const dict_col_t* const new_cols_end __attribute__((unused)) = cols + n_cols; + const dict_v_col_t* const new_v_cols = v_cols; + const dict_v_col_t* const new_v_cols_end __attribute__((unused))= v_cols + n_v_cols; + + cols = old_cols; + col_names = old_col_names; + v_cols = old_v_cols; + v_col_names = old_v_col_names; + n_def = n_cols = old_n_cols & dict_index_t::MAX_N_FIELDS; + n_v_def = n_v_cols = old_n_v_cols & dict_index_t::MAX_N_FIELDS; + n_t_def = n_t_cols = (n_cols + n_v_cols) & dict_index_t::MAX_N_FIELDS; + + if (versioned()) { + for (unsigned i = 0; i < n_cols; ++i) { + if (cols[i].vers_sys_start()) { + vers_start = i & dict_index_t::MAX_N_FIELDS; + } else if (cols[i].vers_sys_end()) { + vers_end = i & dict_index_t::MAX_N_FIELDS; + } + } + } + + index->fields = old_fields; + + while ((index = dict_table_get_next_index(index)) != NULL) { + if (index->to_be_dropped) { + /* instant_column() did not adjust these indexes. */ + continue; + } + + for (unsigned i = 0; i < index->n_fields; i++) { + dict_field_t& f = index->fields[i]; + if (f.col->is_virtual()) { + DBUG_ASSERT(f.col >= &new_v_cols->m_col); + DBUG_ASSERT(f.col < &new_v_cols_end->m_col); + size_t n = size_t( + reinterpret_cast<dict_v_col_t*>(f.col) + - new_v_cols); + DBUG_ASSERT(n <= n_v_cols); + + ulint old_col_no = find_old_col_no( + col_map + n_cols, n, n_v_cols); + DBUG_ASSERT(old_col_no <= n_v_cols); + f.col = &v_cols[old_col_no].m_col; + DBUG_ASSERT(f.col->is_virtual()); + } else { + DBUG_ASSERT(f.col >= new_cols); + DBUG_ASSERT(f.col < new_cols_end); + size_t n = size_t(f.col - new_cols); + DBUG_ASSERT(n <= n_cols); + + ulint old_col_no = find_old_col_no(col_map, + n, n_cols); + DBUG_ASSERT(old_col_no < n_cols); + f.col = &cols[old_col_no]; + DBUG_ASSERT(!f.col->is_virtual()); + } + f.name = f.col->name(*this); + } + } +} + +/* Report an InnoDB error to the client by invoking my_error(). */ +static ATTRIBUTE_COLD __attribute__((nonnull)) +void +my_error_innodb( +/*============*/ + dberr_t error, /*!< in: InnoDB error code */ + const char* table, /*!< in: table name */ + ulint flags) /*!< in: table flags */ +{ + switch (error) { + case DB_MISSING_HISTORY: + my_error(ER_TABLE_DEF_CHANGED, MYF(0)); + break; + case DB_RECORD_NOT_FOUND: + my_error(ER_KEY_NOT_FOUND, MYF(0), table); + break; + case DB_DEADLOCK: + my_error(ER_LOCK_DEADLOCK, MYF(0)); + break; + case DB_LOCK_WAIT_TIMEOUT: + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + break; + case DB_INTERRUPTED: + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + break; + case DB_OUT_OF_MEMORY: + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + break; + case DB_OUT_OF_FILE_SPACE: + my_error(ER_RECORD_FILE_FULL, MYF(0), table); + break; + case DB_TEMP_FILE_WRITE_FAIL: + my_error(ER_TEMP_FILE_WRITE_FAILURE, MYF(0)); + break; + case DB_TOO_BIG_INDEX_COL: + my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), + (ulong) DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(flags)); + break; + case DB_TOO_MANY_CONCURRENT_TRXS: + my_error(ER_TOO_MANY_CONCURRENT_TRXS, MYF(0)); + break; + case DB_LOCK_TABLE_FULL: + my_error(ER_LOCK_TABLE_FULL, MYF(0)); + break; + case DB_UNDO_RECORD_TOO_BIG: + my_error(ER_UNDO_RECORD_TOO_BIG, MYF(0)); + break; + case DB_CORRUPTION: + my_error(ER_NOT_KEYFILE, MYF(0), table); + break; + case DB_TOO_BIG_RECORD: { + /* Note that in page0zip.ic page_zip_rec_needs_ext() rec_size + is limited to COMPRESSED_REC_MAX_DATA_SIZE (16K) or + REDUNDANT_REC_MAX_DATA_SIZE (16K-1). */ + bool comp = !!(flags & DICT_TF_COMPACT); + ulint free_space = page_get_free_space_of_empty(comp) / 2; + + if (free_space >= ulint(comp ? COMPRESSED_REC_MAX_DATA_SIZE : + REDUNDANT_REC_MAX_DATA_SIZE)) { + free_space = (comp ? COMPRESSED_REC_MAX_DATA_SIZE : + REDUNDANT_REC_MAX_DATA_SIZE) - 1; + } + + my_error(ER_TOO_BIG_ROWSIZE, MYF(0), free_space); + break; + } + case DB_INVALID_NULL: + /* TODO: report the row, as we do for DB_DUPLICATE_KEY */ + my_error(ER_INVALID_USE_OF_NULL, MYF(0)); + break; + case DB_CANT_CREATE_GEOMETRY_OBJECT: + my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0)); + break; + case DB_TABLESPACE_EXISTS: + my_error(ER_TABLESPACE_EXISTS, MYF(0), table); + break; + +#ifdef UNIV_DEBUG + case DB_SUCCESS: + case DB_DUPLICATE_KEY: + case DB_ONLINE_LOG_TOO_BIG: + /* These codes should not be passed here. */ + ut_error; +#endif /* UNIV_DEBUG */ + default: + my_error(ER_GET_ERRNO, MYF(0), error, "InnoDB"); + break; + } +} + +/** Get the name of an erroneous key. +@param[in] error_key_num InnoDB number of the erroneus key +@param[in] ha_alter_info changes that were being performed +@param[in] table InnoDB table +@return the name of the erroneous key */ +static +const char* +get_error_key_name( + ulint error_key_num, + const Alter_inplace_info* ha_alter_info, + const dict_table_t* table) +{ + if (error_key_num == ULINT_UNDEFINED) { + return(FTS_DOC_ID_INDEX_NAME); + } else if (ha_alter_info->key_count == 0) { + return(dict_table_get_first_index(table)->name); + } else { + return(ha_alter_info->key_info_buffer[error_key_num].name.str); + } +} + +/** Convert field type and length to InnoDB format */ +static void get_type(const Field &f, uint &prtype, uint8_t &mtype, + uint16_t &len) +{ + mtype= get_innobase_type_from_mysql_type(&prtype, &f); + len= static_cast<uint16_t>(f.pack_length()); + prtype|= f.type(); + if (f.type() == MYSQL_TYPE_VARCHAR) + { + auto l= static_cast<const Field_varstring&>(f).length_bytes; + len= static_cast<uint16_t>(len - l); + if (l == 2) + prtype|= DATA_LONG_TRUE_VARCHAR; + } + if (!f.real_maybe_null()) + prtype |= DATA_NOT_NULL; + if (f.binary()) + prtype |= DATA_BINARY_TYPE; + if (f.table->versioned()) + { + if (&f == f.table->field[f.table->s->vers.start_fieldno]) + prtype|= DATA_VERS_START; + else if (&f == f.table->field[f.table->s->vers.end_fieldno]) + prtype|= DATA_VERS_END; + else if (!(f.flags & VERS_UPDATE_UNVERSIONED_FLAG)) + prtype|= DATA_VERSIONED; + } + + if (!f.stored_in_db()) + prtype|= DATA_VIRTUAL; + + if (dtype_is_string_type(mtype)) + prtype|= f.charset()->number << 16; +} + +struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx +{ + /** Dummy query graph */ + que_thr_t*const thr; + /** The prebuilt struct of the creating instance */ + row_prebuilt_t*& prebuilt; + /** InnoDB indexes being created */ + dict_index_t** add_index; + /** MySQL key numbers for the InnoDB indexes that are being created */ + const ulint* add_key_numbers; + /** number of InnoDB indexes being created */ + ulint num_to_add_index; + /** InnoDB indexes being dropped */ + dict_index_t** drop_index; + /** number of InnoDB indexes being dropped */ + const ulint num_to_drop_index; + /** InnoDB foreign key constraints being dropped */ + dict_foreign_t** drop_fk; + /** number of InnoDB foreign key constraints being dropped */ + const ulint num_to_drop_fk; + /** InnoDB foreign key constraints being added */ + dict_foreign_t** add_fk; + /** number of InnoDB foreign key constraints being dropped */ + const ulint num_to_add_fk; + /** whether to create the indexes online */ + const bool online; + /** memory heap */ + mem_heap_t* const heap; + /** dictionary transaction */ + trx_t* trx; + /** original table (if rebuilt, differs from indexed_table) */ + dict_table_t* old_table; + /** table where the indexes are being created or dropped */ + dict_table_t* new_table; + /** table definition for instant ADD/DROP/reorder COLUMN */ + dict_table_t* instant_table; + /** mapping of old column numbers to new ones, or NULL */ + const ulint* col_map; + /** new column names, or NULL if nothing was renamed */ + const char** col_names; + /** added AUTO_INCREMENT column position, or ULINT_UNDEFINED */ + const ulint add_autoinc; + /** default values of ADD and CHANGE COLUMN, or NULL */ + const dtuple_t* defaults; + /** autoinc sequence to use */ + ib_sequence_t sequence; + /** temporary table name to use for old table when renaming tables */ + const char* tmp_name; + /** whether the order of the clustered index is unchanged */ + bool skip_pk_sort; + /** number of virtual columns to be added */ + unsigned num_to_add_vcol; + /** virtual columns to be added */ + dict_v_col_t* add_vcol; + const char** add_vcol_name; + /** number of virtual columns to be dropped */ + unsigned num_to_drop_vcol; + /** virtual columns to be dropped */ + dict_v_col_t* drop_vcol; + const char** drop_vcol_name; + /** ALTER TABLE stage progress recorder */ + ut_stage_alter_t* m_stage; + /** original number of user columns in the table */ + const unsigned old_n_cols; + /** original columns of the table */ + dict_col_t* const old_cols; + /** original column names of the table */ + const char* const old_col_names; + /** original instantly dropped or reordered columns */ + dict_instant_t* const old_instant; + /** original index fields */ + dict_field_t* const old_fields; + /** size of old_fields */ + const unsigned old_n_fields; + /** original old_table->n_core_fields */ + const unsigned old_n_core_fields; + /** original number of virtual columns in the table */ + const unsigned old_n_v_cols; + /** original virtual columns of the table */ + dict_v_col_t* const old_v_cols; + /** original virtual column names of the table */ + const char* const old_v_col_names; + /** 0, or 1 + first column whose position changes in instant ALTER */ + unsigned first_alter_pos; + /** Allow non-null conversion. + (1) Alter ignore should allow the conversion + irrespective of sql mode. + (2) Don't allow the conversion in strict mode + (3) Allow the conversion only in non-strict mode. */ + const bool allow_not_null; + + /** The page_compression_level attribute, or 0 */ + const uint page_compression_level; + + /** Indexed columns whose charset-collation is changing + in a way that does not require the table to be rebuilt */ + col_collations change_col_collate; + + ha_innobase_inplace_ctx(row_prebuilt_t*& prebuilt_arg, + dict_index_t** drop_arg, + ulint num_to_drop_arg, + dict_foreign_t** drop_fk_arg, + ulint num_to_drop_fk_arg, + dict_foreign_t** add_fk_arg, + ulint num_to_add_fk_arg, + bool online_arg, + mem_heap_t* heap_arg, + dict_table_t* new_table_arg, + const char** col_names_arg, + ulint add_autoinc_arg, + ulonglong autoinc_col_min_value_arg, + ulonglong autoinc_col_max_value_arg, + bool allow_not_null_flag, + bool page_compressed, + ulonglong page_compression_level_arg) : + inplace_alter_handler_ctx(), + thr (pars_complete_graph_for_exec(nullptr, prebuilt_arg->trx, + heap_arg, prebuilt_arg)), + prebuilt (prebuilt_arg), + add_index (0), add_key_numbers (0), num_to_add_index (0), + drop_index (drop_arg), num_to_drop_index (num_to_drop_arg), + drop_fk (drop_fk_arg), num_to_drop_fk (num_to_drop_fk_arg), + add_fk (add_fk_arg), num_to_add_fk (num_to_add_fk_arg), + online (online_arg), heap (heap_arg), + trx (innobase_trx_allocate(prebuilt_arg->trx->mysql_thd)), + old_table (prebuilt_arg->table), + new_table (new_table_arg), instant_table (0), + col_map (0), col_names (col_names_arg), + add_autoinc (add_autoinc_arg), + defaults (0), + sequence(prebuilt->trx->mysql_thd, + autoinc_col_min_value_arg, autoinc_col_max_value_arg), + tmp_name (0), + skip_pk_sort(false), + num_to_add_vcol(0), + add_vcol(0), + add_vcol_name(0), + num_to_drop_vcol(0), + drop_vcol(0), + drop_vcol_name(0), + m_stage(NULL), + old_n_cols(prebuilt_arg->table->n_cols), + old_cols(prebuilt_arg->table->cols), + old_col_names(prebuilt_arg->table->col_names), + old_instant(prebuilt_arg->table->instant), + old_fields(prebuilt_arg->table->indexes.start->fields), + old_n_fields(prebuilt_arg->table->indexes.start->n_fields), + old_n_core_fields(prebuilt_arg->table->indexes.start + ->n_core_fields), + old_n_v_cols(prebuilt_arg->table->n_v_cols), + old_v_cols(prebuilt_arg->table->v_cols), + old_v_col_names(prebuilt_arg->table->v_col_names), + first_alter_pos(0), + allow_not_null(allow_not_null_flag), + page_compression_level(page_compressed + ? (page_compression_level_arg + ? uint(page_compression_level_arg) + : page_zip_level) + : 0) + { + ut_ad(old_n_cols >= DATA_N_SYS_COLS); + ut_ad(page_compression_level <= 9); +#ifdef UNIV_DEBUG + for (ulint i = 0; i < num_to_add_index; i++) { + ut_ad(!add_index[i]->to_be_dropped); + } + for (ulint i = 0; i < num_to_drop_index; i++) { + ut_ad(drop_index[i]->to_be_dropped); + } +#endif /* UNIV_DEBUG */ + + trx_start_for_ddl(trx); + } + + ~ha_innobase_inplace_ctx() + { + UT_DELETE(m_stage); + if (instant_table) { + ut_ad(!instant_table->id); + while (dict_index_t* index + = UT_LIST_GET_LAST(instant_table->indexes)) { + UT_LIST_REMOVE(instant_table->indexes, index); + index->lock.free(); + dict_mem_index_free(index); + } + for (unsigned i = old_n_v_cols; i--; ) { + old_v_cols[i].~dict_v_col_t(); + } + if (instant_table->fts) { + instant_table->fts->~fts_t(); + instant_table->fts = nullptr; + } + dict_mem_table_free(instant_table); + } + mem_heap_free(heap); + } + + /** Determine if the table will be rebuilt. + @return whether the table will be rebuilt */ + bool need_rebuild () const { return(old_table != new_table); } + + /** Convert table-rebuilding ALTER to instant ALTER. */ + void prepare_instant() + { + DBUG_ASSERT(need_rebuild()); + DBUG_ASSERT(!is_instant()); + DBUG_ASSERT(old_table->n_cols == old_n_cols); + + instant_table = new_table; + new_table = old_table; + export_vars.innodb_instant_alter_column++; + + instant_table->prepare_instant(*old_table, col_map, + first_alter_pos); + } + + /** Adjust table metadata for instant ADD/DROP/reorder COLUMN. + @return whether the metadata record must be updated */ + bool instant_column() + { + DBUG_ASSERT(is_instant()); + DBUG_ASSERT(old_n_fields + == old_table->indexes.start->n_fields); + return old_table->instant_column(*instant_table, col_map); + } + + /** Revert prepare_instant() if the transaction is rolled back. */ + void rollback_instant() + { + if (!is_instant()) return; + old_table->rollback_instant(old_n_cols, + old_cols, old_col_names, + old_instant, + old_fields, old_n_fields, + old_n_core_fields, + old_n_v_cols, old_v_cols, + old_v_col_names, + col_map); + } + + /** @return whether this is instant ALTER TABLE */ + bool is_instant() const + { + DBUG_ASSERT(!instant_table || !instant_table->can_be_evicted); + return instant_table; + } + + /** Create an index table where indexes are ordered as follows: + + IF a new primary key is defined for the table THEN + + 1) New primary key + 2) The remaining keys in key_info + + ELSE + + 1) All new indexes in the order they arrive from MySQL + + ENDIF + + @return key definitions */ + MY_ATTRIBUTE((nonnull, warn_unused_result, malloc)) + inline index_def_t* + create_key_defs( + const Alter_inplace_info* ha_alter_info, + /*!< in: alter operation */ + const TABLE* altered_table, + /*!< in: MySQL table that is being altered */ + ulint& n_fts_add, + /*!< out: number of FTS indexes to be created */ + ulint& fts_doc_id_col, + /*!< in: The column number for Doc ID */ + bool& add_fts_doc_id, + /*!< in: whether we need to add new DOC ID + column for FTS index */ + bool& add_fts_doc_idx, + /*!< in: whether we need to add new DOC ID + index for FTS index */ + const TABLE* table); + /*!< in: MySQL table that is being altered */ + + /** Share context between partitions. + @param[in] ctx context from another partition of the table */ + void set_shared_data(const inplace_alter_handler_ctx& ctx) + { + if (add_autoinc != ULINT_UNDEFINED) { + const ha_innobase_inplace_ctx& ha_ctx = + static_cast<const ha_innobase_inplace_ctx&> + (ctx); + /* When adding an AUTO_INCREMENT column to a + partitioned InnoDB table, we must share the + sequence for all partitions. */ + ut_ad(ha_ctx.add_autoinc == add_autoinc); + ut_ad(ha_ctx.sequence.last()); + sequence = ha_ctx.sequence; + } + } + + /** @return whether the given column is being added */ + bool is_new_vcol(const dict_v_col_t &v_col) const + { + for (ulint i= 0; i < num_to_add_vcol; i++) + if (&add_vcol[i] == &v_col) + return true; + return false; + } + + /** During rollback, make newly added indexes point to + newly added virtual columns. */ + void clean_new_vcol_index() + { + ut_ad(old_table == new_table); + const dict_index_t *index= dict_table_get_first_index(old_table); + while ((index= dict_table_get_next_index(index)) != NULL) + { + if (!index->has_virtual() || index->is_committed()) + continue; + ulint n_drop_new_vcol= index->get_new_n_vcol(); + for (ulint i= 0; n_drop_new_vcol && i < index->n_fields; i++) + { + dict_col_t *col= index->fields[i].col; + /* Skip the non-virtual and old virtual columns */ + if (!col->is_virtual()) + continue; + dict_v_col_t *vcol= reinterpret_cast<dict_v_col_t*>(col); + if (!is_new_vcol(*vcol)) + continue; + + index->fields[i].col= &index->new_vcol_info-> + add_drop_v_col(index->heap, vcol, --n_drop_new_vcol)->m_col; + } + } + } + + /** @return whether a FULLTEXT INDEX is being added */ + bool adding_fulltext_index() const + { + for (ulint a= 0; a < num_to_add_index; a++) + if (add_index[a]->type & DICT_FTS) + return true; + return false; + } + + /** Handle the apply log failure for online DDL operation. + @param ha_alter_info handler alter inplace info + @param altered_table MySQL table that is being altered + @param error error code + @retval false if error value is DB_SUCCESS or + TRUE in case of error */ + bool log_failure(Alter_inplace_info *ha_alter_info, + TABLE *altered_table, dberr_t error) + { + ulint err_key= thr_get_trx(thr)->error_key_num; + switch (error) { + KEY *dup_key; + case DB_SUCCESS: + return false; + case DB_DUPLICATE_KEY: + if (err_key == ULINT_UNDEFINED) + /* This should be the hidden index on FTS_DOC_ID */ + dup_key= nullptr; + else + { + DBUG_ASSERT(err_key < ha_alter_info->key_count); + dup_key= &ha_alter_info->key_info_buffer[err_key]; + } + print_keydup_error(altered_table, dup_key, MYF(0)); + break; + case DB_ONLINE_LOG_TOO_BIG: + my_error(ER_INNODB_ONLINE_LOG_TOO_BIG, MYF(0), + get_error_key_name(err_key, ha_alter_info, new_table)); + break; + case DB_INDEX_CORRUPT: + my_error(ER_INDEX_CORRUPT, MYF(0), + get_error_key_name(err_key, ha_alter_info, new_table)); + break; + default: + my_error_innodb(error, old_table->name.m_name, old_table->flags); + } + return true; + } + + /** Check whether the column has any change in collation type. + If it is then store the column information in heap + @param index index being added (or rebuilt) + @param altered_table altered table definition */ + void change_col_collation(dict_index_t *index, const TABLE &altered_table) + { + ut_ad(!need_rebuild()); + ut_ad(!index->is_primary()); + ut_ad(!index->is_committed()); + + unsigned n_cols= 0; + for (unsigned i= 0; i < index->n_fields; i++) + { + const char *field_name= index->fields[i].name(); + if (!field_name || !dtype_is_string_type(index->fields[i].col->mtype) || + index->fields[i].col->is_virtual()) + continue; + for (uint j= 0; j < altered_table.s->fields; j++) + { + const Field *altered_field= altered_table.field[j]; + + if (my_strcasecmp(system_charset_info, field_name, + altered_field->field_name.str)) + continue; + + unsigned prtype; + uint8_t mtype; + uint16_t len; + get_type(*altered_field, prtype, mtype, len); + + if (prtype == index->fields[i].col->prtype) + continue; + auto it= change_col_collate.find(index->fields[i].col->ind); + if (it != change_col_collate.end()) + { + n_cols++; + index->fields[i].col= it->second; + continue; + } + + const CHARSET_INFO *cs= altered_field->charset(); + + dict_col_t *col= + static_cast<dict_col_t*>(mem_heap_alloc(heap, sizeof *col)); + *col= *index->fields[i].col; + col->prtype= prtype; + col->mtype= mtype; + col->mbminlen= cs->mbminlen & 7; + col->mbmaxlen= cs->mbmaxlen & 7; + col->len= len; + index->fields[i].col= col; + n_cols++; + change_col_collate[col->ind]= col; + } + } + + index->init_change_cols(n_cols); + } + + void cleanup_col_collation() + { + ut_ad(old_table == new_table); + if (change_col_collate.empty()) + return; + const dict_index_t *index= dict_table_get_first_index(old_table); + while ((index= dict_table_get_next_index(index)) != nullptr) + { + if (index->is_committed()) + continue; + auto collate_end= change_col_collate.end(); + for (unsigned i= 0, j= 0; i < index->n_fields; i++) + { + const dict_col_t *col= index->fields[i].col; + auto it= change_col_collate.find(col->ind); + if (it != collate_end) + { + ut_ad(it->second == col); + index->fields[i].col= + index->change_col_info->add(index->heap, *col, j++); + } + } + } + } +}; + +/********************************************************************//** +Get the upper limit of the MySQL integral and floating-point type. +@return maximum allowed value for the field */ +ulonglong innobase_get_int_col_max_value(const Field *field); + +/** Determine if fulltext indexes exist in a given table. +@param table MySQL table +@return number of fulltext indexes */ +static uint innobase_fulltext_exist(const TABLE* table) +{ + uint count = 0; + + for (uint i = 0; i < table->s->keys; i++) { + if (table->key_info[i].flags & HA_FULLTEXT) { + count++; + } + } + + return count; +} + +/** Determine whether indexed virtual columns exist in a table. +@param[in] table table definition +@return whether indexes exist on virtual columns */ +static bool innobase_indexed_virtual_exist(const TABLE* table) +{ + const KEY* const end = &table->key_info[table->s->keys]; + + for (const KEY* key = table->key_info; key < end; key++) { + const KEY_PART_INFO* const key_part_end = key->key_part + + key->user_defined_key_parts; + for (const KEY_PART_INFO* key_part = key->key_part; + key_part < key_part_end; key_part++) { + if (!key_part->field->stored_in_db()) + return true; + } + } + + return false; +} + +/** Determine if spatial indexes exist in a given table. +@param table MySQL table +@return whether spatial indexes exist on the table */ +static +bool +innobase_spatial_exist( +/*===================*/ + const TABLE* table) +{ + for (uint i = 0; i < table->s->keys; i++) { + if (table->key_info[i].flags & HA_SPATIAL) { + return(true); + } + } + + return(false); +} + +/** Determine if ALTER_OPTIONS requires rebuilding the table. +@param[in] ha_alter_info the ALTER TABLE operation +@param[in] table metadata before ALTER TABLE +@return whether it is mandatory to rebuild the table */ +static bool alter_options_need_rebuild( + const Alter_inplace_info* ha_alter_info, + const TABLE* table) +{ + DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_OPTIONS); + + if (ha_alter_info->create_info->used_fields + & (HA_CREATE_USED_ROW_FORMAT + | HA_CREATE_USED_KEY_BLOCK_SIZE)) { + /* Specifying ROW_FORMAT or KEY_BLOCK_SIZE requires + rebuilding the table. (These attributes in the .frm + file may disagree with the InnoDB data dictionary, and + the interpretation of thse attributes depends on + InnoDB parameters. That is why we for now always + require a rebuild when these attributes are specified.) */ + return true; + } + + const ha_table_option_struct& alt_opt= + *ha_alter_info->create_info->option_struct; + const ha_table_option_struct& opt= *table->s->option_struct; + + /* Allow an instant change to enable page_compressed, + and any change of page_compression_level. */ + if ((!alt_opt.page_compressed && opt.page_compressed) + || alt_opt.encryption != opt.encryption + || alt_opt.encryption_key_id != opt.encryption_key_id) { + return(true); + } + + return false; +} + +/** Determine if ALTER TABLE needs to rebuild the table +(or perform instant operation). +@param[in] ha_alter_info the ALTER TABLE operation +@param[in] table metadata before ALTER TABLE +@return whether it is necessary to rebuild the table or to alter columns */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_need_rebuild( + const Alter_inplace_info* ha_alter_info, + const TABLE* table) +{ + if ((ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE + | INNOBASE_ALTER_NOREBUILD + | INNOBASE_ALTER_INSTANT)) + == ALTER_OPTIONS) { + return alter_options_need_rebuild(ha_alter_info, table); + } + + return !!(ha_alter_info->handler_flags & INNOBASE_ALTER_REBUILD); +} + +/** Check if virtual column in old and new table are in order, excluding +those dropped column. This is needed because when we drop a virtual column, +ALTER_VIRTUAL_COLUMN_ORDER is also turned on, so we can't decide if this +is a real ORDER change or just DROP COLUMN +@param[in] table old TABLE +@param[in] altered_table new TABLE +@param[in] ha_alter_info Structure describing changes to be done +by ALTER TABLE and holding data used during in-place alter. +@return true is all columns in order, false otherwise. */ +static +bool +check_v_col_in_order( + const TABLE* table, + const TABLE* altered_table, + Alter_inplace_info* ha_alter_info) +{ + ulint j = 0; + + /* We don't support any adding new virtual column before + existed virtual column. */ + if (ha_alter_info->handler_flags + & ALTER_ADD_VIRTUAL_COLUMN) { + bool has_new = false; + + for (const Create_field& new_field : + ha_alter_info->alter_info->create_list) { + if (new_field.stored_in_db()) { + continue; + } + + /* Found a new added virtual column. */ + if (!new_field.field) { + has_new = true; + continue; + } + + /* If there's any old virtual column + after the new added virtual column, + order must be changed. */ + if (has_new) { + return(false); + } + } + } + + /* directly return true if ALTER_VIRTUAL_COLUMN_ORDER is not on */ + if (!(ha_alter_info->handler_flags + & ALTER_VIRTUAL_COLUMN_ORDER)) { + return(true); + } + + for (ulint i = 0; i < table->s->fields; i++) { + Field* field = table->field[i]; + + if (field->stored_in_db()) { + continue; + } + + if (field->flags & FIELD_IS_DROPPED) { + continue; + } + + /* Now check if the next virtual column in altered table + matches this column */ + while (j < altered_table->s->fields) { + Field* new_field = altered_table->s->field[j]; + + if (new_field->stored_in_db()) { + j++; + continue; + } + + if (my_strcasecmp(system_charset_info, + field->field_name.str, + new_field->field_name.str) != 0) { + /* different column */ + return(false); + } else { + j++; + break; + } + } + + if (j > altered_table->s->fields) { + /* there should not be less column in new table + without them being in drop list */ + ut_ad(0); + return(false); + } + } + + return(true); +} + +/** Determine if an instant operation is possible for altering columns. +@param[in] ib_table InnoDB table definition +@param[in] ha_alter_info the ALTER TABLE operation +@param[in] table table definition before ALTER TABLE +@param[in] altered_table table definition after ALTER TABLE +@param[in] strict whether to ensure that user records fit */ +static +bool +instant_alter_column_possible( + const dict_table_t& ib_table, + const Alter_inplace_info* ha_alter_info, + const TABLE* table, + const TABLE* altered_table, + bool strict) +{ + const dict_index_t* const pk = ib_table.indexes.start; + ut_ad(pk->is_primary()); + ut_ad(!pk->has_virtual()); + + if (ha_alter_info->handler_flags + & (ALTER_STORED_COLUMN_ORDER | ALTER_DROP_STORED_COLUMN + | ALTER_ADD_STORED_BASE_COLUMN)) { +#if 1 // MDEV-17459: adjust fts_fetch_doc_from_rec() and friends; remove this + if (ib_table.fts || innobase_fulltext_exist(altered_table)) + return false; +#endif +#if 1 // MDEV-17468: fix bugs with indexed virtual columns & remove this + for (const dict_index_t* index = ib_table.indexes.start; + index; index = index->indexes.next) { + if (index->has_virtual()) { + ut_ad(ib_table.n_v_cols + || index->is_corrupted()); + return false; + } + } +#endif + uint n_add = 0, n_nullable = 0, lenlen = 0; + const uint blob_prefix = dict_table_has_atomic_blobs(&ib_table) + ? 0 + : REC_ANTELOPE_MAX_INDEX_COL_LEN; + const uint min_local_len = blob_prefix + ? blob_prefix + FIELD_REF_SIZE + : 2 * FIELD_REF_SIZE; + size_t min_size = 0, max_size = 0; + Field** af = altered_table->field; + Field** const end = altered_table->field + + altered_table->s->fields; + List_iterator_fast<Create_field> cf_it( + ha_alter_info->alter_info->create_list); + + for (; af < end; af++) { + const Create_field* cf = cf_it++; + if (!(*af)->stored_in_db() || cf->field) { + /* Virtual or pre-existing column */ + continue; + } + const bool nullable = (*af)->real_maybe_null(); + const bool is_null = (*af)->is_real_null(); + ut_ad(!is_null || nullable); + n_nullable += nullable; + n_add++; + uint l; + switch ((*af)->type()) { + case MYSQL_TYPE_VARCHAR: + l = reinterpret_cast<const Field_varstring*> + (*af)->get_length(); + variable_length: + if (l >= min_local_len) { + max_size += blob_prefix + + FIELD_REF_SIZE; + if (!is_null) { + min_size += blob_prefix + + FIELD_REF_SIZE; + } + lenlen += 2; + } else { + if (!is_null) { + min_size += l; + } + l = (*af)->pack_length(); + max_size += l; + lenlen += l > 255 ? 2 : 1; + } + break; + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + l = reinterpret_cast<const Field_blob*> + ((*af))->get_length(); + goto variable_length; + default: + l = (*af)->pack_length(); + if (l > 255 && ib_table.not_redundant()) { + goto variable_length; + } + max_size += l; + if (!is_null) { + min_size += l; + } + } + } + + ulint n_fields = pk->n_fields + n_add; + + if (n_fields >= REC_MAX_N_USER_FIELDS + DATA_N_SYS_COLS) { + return false; + } + + if (pk->is_gen_clust()) { + min_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN + + DATA_ROW_ID_LEN; + max_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN + + DATA_ROW_ID_LEN; + } else { + min_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + max_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + } + + uint i = pk->n_fields; + while (i-- > pk->n_core_fields) { + const dict_field_t& f = pk->fields[i]; + if (f.col->is_nullable()) { + n_nullable++; + if (!f.col->is_dropped() + && f.col->def_val.data) { + goto instantly_added_column; + } + } else if (f.fixed_len + && (f.fixed_len <= 255 + || !ib_table.not_redundant())) { + if (ib_table.not_redundant() + || !f.col->is_dropped()) { + min_size += f.fixed_len; + max_size += f.fixed_len; + } + } else if (f.col->is_dropped() || !f.col->is_added()) { + lenlen++; + goto set_max_size; + } else { +instantly_added_column: + ut_ad(f.col->is_added()); + if (f.col->def_val.len >= min_local_len) { + min_size += blob_prefix + + FIELD_REF_SIZE; + lenlen += 2; + } else { + min_size += f.col->def_val.len; + lenlen += f.col->def_val.len + > 255 ? 2 : 1; + } +set_max_size: + if (f.fixed_len + && (f.fixed_len <= 255 + || !ib_table.not_redundant())) { + max_size += f.fixed_len; + } else if (f.col->len >= min_local_len) { + max_size += blob_prefix + + FIELD_REF_SIZE; + } else { + max_size += f.col->len; + } + } + } + + do { + const dict_field_t& f = pk->fields[i]; + if (f.col->is_nullable()) { + n_nullable++; + } else if (f.fixed_len) { + min_size += f.fixed_len; + } else { + lenlen++; + } + } while (i--); + + if (ib_table.instant + || (ha_alter_info->handler_flags + & (ALTER_STORED_COLUMN_ORDER + | ALTER_DROP_STORED_COLUMN))) { + n_fields++; + lenlen += 2; + min_size += FIELD_REF_SIZE; + } + + if (ib_table.not_redundant()) { + min_size += REC_N_NEW_EXTRA_BYTES + + UT_BITS_IN_BYTES(n_nullable) + + lenlen; + } else { + min_size += (n_fields > 255 || min_size > 255) + ? n_fields * 2 : n_fields; + min_size += REC_N_OLD_EXTRA_BYTES; + } + + if (page_zip_rec_needs_ext(min_size, ib_table.not_redundant(), + 0, 0)) { + return false; + } + + if (strict && page_zip_rec_needs_ext(max_size, + ib_table.not_redundant(), + 0, 0)) { + return false; + } + } + // Making table system-versioned instantly is not implemented yet. + if (ha_alter_info->handler_flags & ALTER_ADD_SYSTEM_VERSIONING) { + return false; + } + + static constexpr alter_table_operations avoid_rebuild + = ALTER_ADD_STORED_BASE_COLUMN + | ALTER_DROP_STORED_COLUMN + | ALTER_STORED_COLUMN_ORDER + | ALTER_COLUMN_NULLABLE; + + if (!(ha_alter_info->handler_flags & avoid_rebuild)) { + alter_table_operations flags = ha_alter_info->handler_flags + & ~avoid_rebuild; + /* None of the flags are set that we can handle + specially to avoid rebuild. In this case, we can + allow ALGORITHM=INSTANT, except if some requested + operation requires that the table be rebuilt. */ + if (flags & INNOBASE_ALTER_REBUILD) { + return false; + } + if ((flags & ALTER_OPTIONS) + && alter_options_need_rebuild(ha_alter_info, table)) { + return false; + } + } else if (!ib_table.supports_instant()) { + return false; + } + + /* At the moment, we disallow ADD [UNIQUE] INDEX together with + instant ADD COLUMN. + + The main reason is that the work of instant ADD must be done + in commit_inplace_alter_table(). For the rollback_instant() + to work, we must add the columns to dict_table_t beforehand, + and roll back those changes in case the transaction is rolled + back. + + If we added the columns to the dictionary cache already in the + prepare_inplace_alter_table(), we would have to deal with + column number mismatch in ha_innobase::open(), write_row() and + other functions. */ + + /* FIXME: allow instant ADD COLUMN together with + INNOBASE_ONLINE_CREATE (ADD [UNIQUE] INDEX) on pre-existing + columns. */ + if (ha_alter_info->handler_flags + & ((INNOBASE_ALTER_REBUILD | INNOBASE_ONLINE_CREATE) + & ~ALTER_DROP_STORED_COLUMN + & ~ALTER_STORED_COLUMN_ORDER + & ~ALTER_ADD_STORED_BASE_COLUMN + & ~ALTER_COLUMN_NULLABLE + & ~ALTER_OPTIONS)) { + return false; + } + + if ((ha_alter_info->handler_flags & ALTER_OPTIONS) + && alter_options_need_rebuild(ha_alter_info, table)) { + return false; + } + + if (ha_alter_info->handler_flags & ALTER_COLUMN_NULLABLE) { + if (ib_table.not_redundant()) { + /* Instantaneous removal of NOT NULL is + only supported for ROW_FORMAT=REDUNDANT. */ + return false; + } + if (ib_table.fts_doc_id_index + && !innobase_fulltext_exist(altered_table)) { + /* Removing hidden FTS_DOC_ID_INDEX(FTS_DOC_ID) + requires that the table be rebuilt. */ + return false; + } + + Field** af = altered_table->field; + Field** const end = altered_table->field + + altered_table->s->fields; + List_iterator_fast<Create_field> cf_it( + ha_alter_info->alter_info->create_list); + for (unsigned c = 0; af < end; af++) { + const Create_field* cf = cf_it++; + if (!cf->field || !(*af)->stored_in_db()) { + /* Ignore virtual or newly created + column */ + continue; + } + + const dict_col_t* col = dict_table_get_nth_col( + &ib_table, c++); + + if (!col->ord_part || col->is_nullable() + || !(*af)->real_maybe_null()) { + continue; + } + + /* The column would be changed from NOT NULL. + Ensure that it is not a clustered index key. */ + for (auto i = pk->n_uniq; i--; ) { + if (pk->fields[i].col == col) { + return false; + } + } + } + } + + return true; +} + +/** Check whether the non-const default value for the field +@param[in] field field which could be added or changed +@return true if the non-const default is present. */ +static bool is_non_const_value(Field* field) +{ + return field->default_value + && field->default_value->flags + & uint(~(VCOL_SESSION_FUNC | VCOL_TIME_FUNC)); +} + +/** Set default value for the field. +@param[in] field field which could be added or changed +@return true if the default value is set. */ +static bool set_default_value(Field* field) +{ + /* The added/changed NOT NULL column lacks a DEFAULT value, + or the DEFAULT is the same for all rows. + (Time functions, such as CURRENT_TIMESTAMP(), + are evaluated from a timestamp that is assigned + at the start of the statement. Session + functions, such as USER(), always evaluate the + same within a statement.) */ + + ut_ad(!is_non_const_value(field)); + + /* Compute the DEFAULT values of non-constant columns + (VCOL_SESSION_FUNC | VCOL_TIME_FUNC). */ + switch (field->set_default()) { + case 0: /* OK */ + case 3: /* DATETIME to TIME or DATE conversion */ + return true; + case -1: /* OOM, or GEOMETRY type mismatch */ + case 1: /* A number adjusted to the min/max value */ + case 2: /* String truncation, or conversion problem */ + break; + } + + return false; +} + +/** Check whether the table has the FTS_DOC_ID column +@param[in] table InnoDB table with fulltext index +@param[in] altered_table MySQL table with fulltext index +@param[out] fts_doc_col_no The column number for Doc ID, + or ULINT_UNDEFINED if it is of wrong type +@param[out] num_v Number of virtual column +@param[in] check_only check only whether fts doc id exist. +@return whether there exists an FTS_DOC_ID column */ +static +bool +innobase_fts_check_doc_id_col( + const dict_table_t* table, + const TABLE* altered_table, + ulint* fts_doc_col_no, + ulint* num_v, + bool check_only=false) +{ + *fts_doc_col_no = ULINT_UNDEFINED; + + const uint n_cols = altered_table->s->fields; + ulint i; + int err = 0; + *num_v = 0; + + for (i = 0; i < n_cols; i++) { + const Field* field = altered_table->field[i]; + + if (!field->stored_in_db()) { + (*num_v)++; + } + + if (my_strcasecmp(system_charset_info, + field->field_name.str, FTS_DOC_ID_COL_NAME)) { + continue; + } + + if (strcmp(field->field_name.str, FTS_DOC_ID_COL_NAME)) { + err = ER_WRONG_COLUMN_NAME; + } else if (field->type() != MYSQL_TYPE_LONGLONG + || field->pack_length() != 8 + || field->real_maybe_null() + || !(field->flags & UNSIGNED_FLAG) + || !field->stored_in_db()) { + err = ER_INNODB_FT_WRONG_DOCID_COLUMN; + } else { + *fts_doc_col_no = i - *num_v; + } + + if (err && !check_only) { + my_error(err, MYF(0), field->field_name.str); + } + + return(true); + } + + if (!table) { + return(false); + } + + /* Not to count the virtual columns */ + i -= *num_v; + + for (; i + DATA_N_SYS_COLS < (uint) table->n_cols; i++) { + const char* name = dict_table_get_col_name(table, i); + + if (strcmp(name, FTS_DOC_ID_COL_NAME) == 0) { +#ifdef UNIV_DEBUG + const dict_col_t* col; + + col = dict_table_get_nth_col(table, i); + + /* Because the FTS_DOC_ID does not exist in + the .frm file or TABLE_SHARE, this must be the + internally created FTS_DOC_ID column. */ + ut_ad(col->mtype == DATA_INT); + ut_ad(col->len == 8); + ut_ad(col->prtype & DATA_NOT_NULL); + ut_ad(col->prtype & DATA_UNSIGNED); +#endif /* UNIV_DEBUG */ + *fts_doc_col_no = i; + return(true); + } + } + + return(false); +} + +/** Check whether the table is empty. +@param[in] table table to be checked +@param[in] ignore_delete_marked Ignore the delete marked + flag record +@return true if table is empty */ +static bool innobase_table_is_empty(const dict_table_t *table, + bool ignore_delete_marked=true) +{ + if (!table->space) + return false; + dict_index_t *clust_index= dict_table_get_first_index(table); + mtr_t mtr; + btr_pcur_t pcur; + buf_block_t *block; + page_cur_t *cur; + rec_t *rec; + bool next_page= false; + + mtr.start(); + if (pcur.open_leaf(true, clust_index, BTR_SEARCH_LEAF, &mtr) != DB_SUCCESS) + { +non_empty: + mtr.commit(); + return false; + } + rec= page_rec_get_next(btr_pcur_get_rec(&pcur)); + if (UNIV_UNLIKELY(!rec)) + goto non_empty; + if (rec_is_metadata(rec, *clust_index)) + btr_pcur_get_page_cur(&pcur)->rec= rec; +scan_leaf: + cur= btr_pcur_get_page_cur(&pcur); + if (UNIV_UNLIKELY(!page_cur_move_to_next(cur))) + goto non_empty; +next_page: + if (next_page) + { + uint32_t next_page_no= btr_page_get_next(page_cur_get_page(cur)); + if (next_page_no == FIL_NULL) + { + mtr.commit(); + return true; + } + + next_page= false; + block= btr_block_get(*clust_index, next_page_no, RW_S_LATCH, false, &mtr); + if (!block) + goto non_empty; + page_cur_set_before_first(block, cur); + if (UNIV_UNLIKELY(!page_cur_move_to_next(cur))) + goto non_empty; + const auto s= mtr.get_savepoint(); + mtr.rollback_to_savepoint(s - 2, s - 1); + } + + rec= page_cur_get_rec(cur); + if (rec_get_deleted_flag(rec, dict_table_is_comp(table))) + { + if (ignore_delete_marked) + goto scan_leaf; + goto non_empty; + } + else if (!page_rec_is_supremum(rec)) + goto non_empty; + else + { + next_page= true; + goto next_page; + } + goto scan_leaf; +} + +/** Check if InnoDB supports a particular alter table in-place +@param altered_table TABLE object for new version of table. +@param ha_alter_info Structure describing changes to be done +by ALTER TABLE and holding data used during in-place alter. + +@retval HA_ALTER_INPLACE_NOT_SUPPORTED Not supported +@retval HA_ALTER_INPLACE_INSTANT +MDL_EXCLUSIVE is needed for executing prepare_inplace_alter_table() +and commit_inplace_alter_table(). inplace_alter_table() will not be called. +@retval HA_ALTER_INPLACE_COPY_NO_LOCK +MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded to +LOCK=NONE for rebuilding the table in inplace_alter_table() +@retval HA_ALTER_INPLACE_COPY_LOCK +MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded to +LOCK=SHARED for rebuilding the table in inplace_alter_table() +@retval HA_ALTER_INPLACE_NOCOPY_NO_LOCK +MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded to +LOCK=NONE for inplace_alter_table() which will not rebuild the table +@retval HA_ALTER_INPLACE_NOCOPY_LOCK +MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded to +LOCK=SHARED for inplace_alter_table() which will not rebuild the table +*/ + +enum_alter_inplace_result +ha_innobase::check_if_supported_inplace_alter( + TABLE* altered_table, + Alter_inplace_info* ha_alter_info) +{ + DBUG_ENTER("check_if_supported_inplace_alter"); + + if ((ha_alter_info->handler_flags + & INNOBASE_ALTER_VERSIONED_REBUILD) + && altered_table->versioned(VERS_TIMESTAMP)) { + ha_alter_info->unsupported_reason = + "Not implemented for system-versioned timestamp tables"; + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + /* Before 10.2.2 information about virtual columns was not stored in + system tables. We need to do a full alter to rebuild proper 10.2.2+ + metadata with the information about virtual columns */ + if (omits_virtual_cols(*table_share)) { + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + if (altered_table->s->fields > REC_MAX_N_USER_FIELDS) { + /* Deny the inplace ALTER TABLE. MySQL will try to + re-create the table and ha_innobase::create() will + return an error too. This is how we effectively + deny adding too many columns to a table. */ + ha_alter_info->unsupported_reason = + my_get_err_msg(ER_TOO_MANY_FIELDS); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + update_thd(); + + if (!m_prebuilt->table->space) { + ib_senderrf(m_user_thd, IB_LOG_LEVEL_WARN, + ER_TABLESPACE_DISCARDED, + table->s->table_name.str); + } + + if (is_read_only(!high_level_read_only + && (ha_alter_info->handler_flags & ALTER_OPTIONS) + && ha_alter_info->create_info->key_block_size == 0 + && ha_alter_info->create_info->row_type + != ROW_TYPE_COMPRESSED)) { + ha_alter_info->unsupported_reason = + my_get_err_msg(ER_READ_ONLY_MODE); + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + if (ha_alter_info->handler_flags + & ~(INNOBASE_INPLACE_IGNORE + | INNOBASE_ALTER_INSTANT + | INNOBASE_ALTER_NOREBUILD + | INNOBASE_ALTER_REBUILD + | ALTER_INDEX_IGNORABILITY)) { + + if (ha_alter_info->handler_flags + & ALTER_STORED_COLUMN_TYPE) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE); + } + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + ut_ad(dict_sys.sys_tables_exist()); + + /* Only support online add foreign key constraint when + check_foreigns is turned off */ + if ((ha_alter_info->handler_flags & ALTER_ADD_FOREIGN_KEY) + && m_prebuilt->trx->check_foreigns) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + const char* reason_rebuild = NULL; + + switch (innodb_instant_alter_column_allowed) { + case 0: /* never */ + if ((ha_alter_info->handler_flags + & (ALTER_ADD_STORED_BASE_COLUMN + | ALTER_STORED_COLUMN_ORDER + | ALTER_DROP_STORED_COLUMN)) + || m_prebuilt->table->is_instant()) { + reason_rebuild = + "innodb_instant_alter_column_allowed=never"; +innodb_instant_alter_column_allowed_reason: + if (ha_alter_info->handler_flags + & ALTER_RECREATE_TABLE) { + reason_rebuild = NULL; + } else { + ha_alter_info->handler_flags + |= ALTER_RECREATE_TABLE; + ha_alter_info->unsupported_reason + = reason_rebuild; + } + } + break; + case 1: /* add_last */ + if ((ha_alter_info->handler_flags + & (ALTER_STORED_COLUMN_ORDER | ALTER_DROP_STORED_COLUMN)) + || m_prebuilt->table->instant) { + reason_rebuild = "innodb_instant_atler_column_allowed=" + "add_last"; + goto innodb_instant_alter_column_allowed_reason; + } + } + + switch (ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) { + case ALTER_OPTIONS: + if (alter_options_need_rebuild(ha_alter_info, table)) { + reason_rebuild = my_get_err_msg( + ER_ALTER_OPERATION_TABLE_OPTIONS_NEED_REBUILD); + ha_alter_info->unsupported_reason = reason_rebuild; + break; + } + /* fall through */ + case 0: + DBUG_RETURN(HA_ALTER_INPLACE_INSTANT); + } + + /* InnoDB cannot IGNORE when creating unique indexes. IGNORE + should silently delete some duplicate rows. Our inplace_alter + code will not delete anything from existing indexes. */ + if (ha_alter_info->ignore + && (ha_alter_info->handler_flags + & (ALTER_ADD_PK_INDEX | ALTER_ADD_UNIQUE_INDEX))) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_IGNORE); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + /* DROP PRIMARY KEY is only allowed in combination with ADD + PRIMARY KEY. */ + if ((ha_alter_info->handler_flags + & (ALTER_ADD_PK_INDEX | ALTER_DROP_PK_INDEX)) + == ALTER_DROP_PK_INDEX) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + if (ha_alter_info->handler_flags & ALTER_COLUMN_NULLABLE) { + /* If a NOT NULL attribute is going to be removed and + a UNIQUE INDEX on the column had been promoted to an + implicit PRIMARY KEY, the table should be rebuilt by + ALGORITHM=COPY. (Theoretically, we could support + rebuilding by ALGORITHM=INPLACE if a PRIMARY KEY is + going to be added, either explicitly or by promoting + another UNIQUE KEY.) */ + const uint my_primary_key = altered_table->s->primary_key; + + if (UNIV_UNLIKELY(my_primary_key >= MAX_KEY) + && !dict_index_is_auto_gen_clust( + dict_table_get_first_index(m_prebuilt->table))) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_PRIMARY_CANT_HAVE_NULL); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + } + + /* + InnoDB in different MariaDB versions was generating different mtype + codes for certain types. In some cases the signed/unsigned bit was + generated differently too. + + Inplace ALTER would change the mtype/unsigned_flag (to what the + current code generates) without changing the underlying data + represenation, and it might result in data corruption. + + Don't do inplace ALTER if mtype/unsigned_flag are wrong. + */ + for (ulint i = 0, icol= 0; i < table->s->fields; i++) { + const Field* field = table->field[i]; + const dict_col_t* col = dict_table_get_nth_col( + m_prebuilt->table, icol); + unsigned unsigned_flag; + + if (!field->stored_in_db()) { + continue; + } + + icol++; + + if (col->mtype != get_innobase_type_from_mysql_type( + &unsigned_flag, field)) { + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + if ((col->prtype & DATA_UNSIGNED) != unsigned_flag) { + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + } + + ulint n_indexes = UT_LIST_GET_LEN((m_prebuilt->table)->indexes); + + /* If InnoDB dictionary and MySQL frm file are not consistent + use "Copy" method. */ + if (m_prebuilt->table->dict_frm_mismatch) { + + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_NO_SUCH_INDEX); + ib_push_frm_error(m_user_thd, m_prebuilt->table, altered_table, + n_indexes, true); + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + /* '0000-00-00' value isn't allowed for datetime datatype + for newly added column when table is not empty */ + if (ha_alter_info->error_if_not_empty + && m_prebuilt->table->space + && !innobase_table_is_empty(m_prebuilt->table)) { + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + const bool add_drop_v_cols = !!(ha_alter_info->handler_flags + & (ALTER_ADD_VIRTUAL_COLUMN + | ALTER_DROP_VIRTUAL_COLUMN + | ALTER_VIRTUAL_COLUMN_ORDER)); + + /* We should be able to do the operation in-place. + See if we can do it online (LOCK=NONE) or without rebuild. */ + bool online = true, need_rebuild = false; + const uint fulltext_indexes = innobase_fulltext_exist(altered_table); + + /* Fix the key parts. */ + for (KEY* new_key = ha_alter_info->key_info_buffer; + new_key < ha_alter_info->key_info_buffer + + ha_alter_info->key_count; + new_key++) { + + /* Do not support adding/droping a virtual column, while + there is a table rebuild caused by adding a new FTS_DOC_ID */ + if ((new_key->flags & HA_FULLTEXT) && add_drop_v_cols + && !DICT_TF2_FLAG_IS_SET(m_prebuilt->table, + DICT_TF2_FTS_HAS_DOC_ID)) { + ha_alter_info->unsupported_reason = + MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN; + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + for (KEY_PART_INFO* key_part = new_key->key_part; + key_part < (new_key->key_part + + new_key->user_defined_key_parts); + key_part++) { + DBUG_ASSERT(key_part->fieldnr + < altered_table->s->fields); + + const Create_field* new_field + = ha_alter_info->alter_info->create_list.elem( + key_part->fieldnr); + + DBUG_ASSERT(new_field); + + key_part->field = altered_table->field[ + key_part->fieldnr]; + + /* In some special cases InnoDB emits "false" + duplicate key errors with NULL key values. Let + us play safe and ensure that we can correctly + print key values even in such cases. */ + key_part->null_offset = key_part->field->null_offset(); + key_part->null_bit = key_part->field->null_bit; + + if (new_field->field) { + /* This is an existing column. */ + continue; + } + + /* This is an added column. */ + DBUG_ASSERT(ha_alter_info->handler_flags + & ALTER_ADD_COLUMN); + + /* We cannot replace a hidden FTS_DOC_ID + with a user-visible FTS_DOC_ID. */ + if (fulltext_indexes && m_prebuilt->table->fts + && !my_strcasecmp( + system_charset_info, + key_part->field->field_name.str, + FTS_DOC_ID_COL_NAME)) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + DBUG_ASSERT((key_part->field->unireg_check + == Field::NEXT_NUMBER) + == !!(key_part->field->flags + & AUTO_INCREMENT_FLAG)); + + if (key_part->field->flags & AUTO_INCREMENT_FLAG) { + /* We cannot assign AUTO_INCREMENT values + during online or instant ALTER. */ + DBUG_ASSERT(key_part->field == altered_table + -> found_next_number_field); + + if (ha_alter_info->online) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC); + } + + online = false; + need_rebuild = true; + } + + if (!key_part->field->stored_in_db()) { + /* Do not support adding index on newly added + virtual column, while there is also a drop + virtual column in the same clause */ + if (ha_alter_info->handler_flags + & ALTER_DROP_VIRTUAL_COLUMN) { + ha_alter_info->unsupported_reason = + MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN; + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + if (ha_alter_info->online + && !ha_alter_info->unsupported_reason) { + ha_alter_info->unsupported_reason = + MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN; + } + + online = false; + } + } + } + + DBUG_ASSERT(!m_prebuilt->table->fts + || (m_prebuilt->table->fts->doc_col <= table->s->fields)); + + DBUG_ASSERT(!m_prebuilt->table->fts + || (m_prebuilt->table->fts->doc_col + < dict_table_get_n_user_cols(m_prebuilt->table))); + + if (fulltext_indexes && m_prebuilt->table->fts) { + /* FTS index of versioned table has row_end, need rebuild */ + if (table->versioned() != altered_table->versioned()) { + need_rebuild= true; + } + + /* FULLTEXT indexes are supposed to remain. */ + /* Disallow DROP INDEX FTS_DOC_ID_INDEX */ + + for (uint i = 0; i < ha_alter_info->index_drop_count; i++) { + if (!my_strcasecmp( + system_charset_info, + ha_alter_info->index_drop_buffer[i]->name.str, + FTS_DOC_ID_INDEX_NAME)) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + } + + /* InnoDB can have a hidden FTS_DOC_ID_INDEX on a + visible FTS_DOC_ID column as well. Prevent dropping or + renaming the FTS_DOC_ID. */ + + for (Field** fp = table->field; *fp; fp++) { + if (!((*fp)->flags + & (FIELD_IS_RENAMED | FIELD_IS_DROPPED))) { + continue; + } + + if (!my_strcasecmp( + system_charset_info, + (*fp)->field_name.str, + FTS_DOC_ID_COL_NAME)) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + } + } + + m_prebuilt->trx->will_lock = true; + + /* When changing a NULL column to NOT NULL and specifying a + DEFAULT value, ensure that the DEFAULT expression is a constant. + Also, in ADD COLUMN, for now we only support a + constant DEFAULT expression. */ + Field **af = altered_table->field; + bool fts_need_rebuild = false; + need_rebuild = need_rebuild + || innobase_need_rebuild(ha_alter_info, table); + + for (Create_field& cf : ha_alter_info->alter_info->create_list) { + DBUG_ASSERT(cf.field + || (ha_alter_info->handler_flags + & ALTER_ADD_COLUMN)); + + if (const Field* f = cf.field) { + /* An AUTO_INCREMENT attribute can only + be added to an existing column by ALGORITHM=COPY, + but we can remove the attribute. */ + ut_ad((*af)->unireg_check != Field::NEXT_NUMBER + || f->unireg_check == Field::NEXT_NUMBER); + if (!f->real_maybe_null() || (*af)->real_maybe_null()) + goto next_column; + /* We are changing an existing column + from NULL to NOT NULL. */ + DBUG_ASSERT(ha_alter_info->handler_flags + & ALTER_COLUMN_NOT_NULLABLE); + /* Virtual columns are never NOT NULL. */ + DBUG_ASSERT(f->stored_in_db()); + switch ((*af)->type()) { + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: + /* Inserting NULL into a TIMESTAMP column + would cause the DEFAULT value to be + replaced. Ensure that the DEFAULT + expression is not changing during + ALTER TABLE. */ + if (!(*af)->default_value + && (*af)->is_real_null()) { + /* No DEFAULT value is + specified. We can report + errors for any NULL values for + the TIMESTAMP. */ + goto next_column; + } + break; + default: + /* For any other data type, NULL + values are not converted. */ + goto next_column; + } + + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL); + } else if (!is_non_const_value(*af) + && set_default_value(*af)) { + if (fulltext_indexes > 1 + && !my_strcasecmp(system_charset_info, + (*af)->field_name.str, + FTS_DOC_ID_COL_NAME)) { + /* If a hidden FTS_DOC_ID column exists + (because of FULLTEXT INDEX), it cannot + be replaced with a user-created one + except when using ALGORITHM=COPY. */ + ha_alter_info->unsupported_reason = + my_get_err_msg(ER_INNODB_FT_LIMIT); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + goto next_column; + } + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + +next_column: + af++; + } + + const bool supports_instant = instant_alter_column_possible( + *m_prebuilt->table, ha_alter_info, table, altered_table, + is_innodb_strict_mode()); + if (add_drop_v_cols) { + ulonglong flags = ha_alter_info->handler_flags; + + /* TODO: uncomment the flags below, once we start to + support them */ + + flags &= ~(ALTER_ADD_VIRTUAL_COLUMN + | ALTER_DROP_VIRTUAL_COLUMN + | ALTER_VIRTUAL_COLUMN_ORDER + | ALTER_VIRTUAL_GCOL_EXPR + | ALTER_COLUMN_VCOL + /* + | ALTER_ADD_STORED_BASE_COLUMN + | ALTER_DROP_STORED_COLUMN + | ALTER_STORED_COLUMN_ORDER + | ALTER_ADD_UNIQUE_INDEX + */ + | ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX + | ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX + | ALTER_INDEX_ORDER); + if (supports_instant) { + flags &= ~(ALTER_DROP_STORED_COLUMN +#if 0 /* MDEV-17468: remove check_v_col_in_order() and fix the code */ + | ALTER_ADD_STORED_BASE_COLUMN +#endif + | ALTER_STORED_COLUMN_ORDER); + } + if (flags != 0 + || IF_PARTITIONING((altered_table->s->partition_info_str + && altered_table->s->partition_info_str_len), 0) + || (!check_v_col_in_order( + this->table, altered_table, ha_alter_info))) { + ha_alter_info->unsupported_reason = + MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN; + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + } + + if (supports_instant && !(ha_alter_info->handler_flags + & INNOBASE_ALTER_NOREBUILD)) { + DBUG_RETURN(HA_ALTER_INPLACE_INSTANT); + } + + if (need_rebuild + && (fulltext_indexes + || innobase_spatial_exist(altered_table) + || innobase_indexed_virtual_exist(altered_table))) { + /* If the table already contains fulltext indexes, + refuse to rebuild the table natively altogether. */ + if (fulltext_indexes > 1) { +cannot_create_many_fulltext_index: + ha_alter_info->unsupported_reason = + my_get_err_msg(ER_INNODB_FT_LIMIT); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + if (!online || !ha_alter_info->online + || ha_alter_info->unsupported_reason != reason_rebuild) { + /* Either LOCK=NONE was not requested, or we already + gave specific reason to refuse it. */ + } else if (fulltext_indexes) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS); + } else if (innobase_spatial_exist(altered_table)) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS); + } else { + /* MDEV-14341 FIXME: Remove this limitation. */ + ha_alter_info->unsupported_reason = + "online rebuild with indexed virtual columns"; + } + + online = false; + } + + if (ha_alter_info->handler_flags + & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) { + /* ADD FULLTEXT|SPATIAL INDEX requires a lock. + + We could do ADD FULLTEXT INDEX without a lock if the + table already contains an FTS_DOC_ID column, but in + that case we would have to apply the modification log + to the full-text indexes. + + We could also do ADD SPATIAL INDEX by implementing + row_log_apply() for it. */ + bool add_fulltext = false; + + for (uint i = 0; i < ha_alter_info->index_add_count; i++) { + const KEY* key = + &ha_alter_info->key_info_buffer[ + ha_alter_info->index_add_buffer[i]]; + if (key->flags & HA_FULLTEXT) { + DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK + & ~(HA_FULLTEXT + | HA_PACK_KEY + | HA_GENERATED_KEY + | HA_BINARY_PACK_KEY))); + if (add_fulltext) { + goto cannot_create_many_fulltext_index; + } + + add_fulltext = true; + if (ha_alter_info->online + && !ha_alter_info->unsupported_reason) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS); + } + + online = false; + + /* Full text search index exists, check + whether the table already has DOC ID column. + If not, InnoDB have to rebuild the table to + add a Doc ID hidden column and change + primary index. */ + ulint fts_doc_col_no; + ulint num_v = 0; + + fts_need_rebuild = + !innobase_fts_check_doc_id_col( + m_prebuilt->table, + altered_table, + &fts_doc_col_no, &num_v, true); + } + + if (online && (key->flags & HA_SPATIAL)) { + + if (ha_alter_info->online) { + ha_alter_info->unsupported_reason = my_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS); + } + + online = false; + } + } + } + + // FIXME: implement Online DDL for system-versioned operations + if (ha_alter_info->handler_flags & INNOBASE_ALTER_VERSIONED_REBUILD) { + + if (ha_alter_info->online) { + ha_alter_info->unsupported_reason = + "Not implemented for system-versioned operations"; + } + + online = false; + } + + if ((need_rebuild && !supports_instant) || fts_need_rebuild) { + ha_alter_info->handler_flags |= ALTER_RECREATE_TABLE; + DBUG_RETURN(online + ? HA_ALTER_INPLACE_COPY_NO_LOCK + : HA_ALTER_INPLACE_COPY_LOCK); + } + + if (ha_alter_info->unsupported_reason) { + } else if (ha_alter_info->handler_flags & INNOBASE_ONLINE_CREATE) { + ha_alter_info->unsupported_reason = "ADD INDEX"; + } else { + ha_alter_info->unsupported_reason = "DROP INDEX"; + } + + DBUG_RETURN(online + ? HA_ALTER_INPLACE_NOCOPY_NO_LOCK + : HA_ALTER_INPLACE_NOCOPY_LOCK); +} + +/*************************************************************//** +Initialize the dict_foreign_t structure with supplied info +@return true if added, false if duplicate foreign->id */ +static MY_ATTRIBUTE((nonnull(1,3,5,7))) +bool +innobase_init_foreign( +/*==================*/ + dict_foreign_t* foreign, /*!< in/out: structure to + initialize */ + const char* constraint_name, /*!< in/out: constraint name if + exists */ + dict_table_t* table, /*!< in: foreign table */ + dict_index_t* index, /*!< in: foreign key index */ + const char** column_names, /*!< in: foreign key column + names */ + ulint num_field, /*!< in: number of columns */ + const char* referenced_table_name, /*!< in: referenced table + name */ + dict_table_t* referenced_table, /*!< in: referenced table */ + dict_index_t* referenced_index, /*!< in: referenced index */ + const char** referenced_column_names,/*!< in: referenced column + names */ + ulint referenced_num_field) /*!< in: number of referenced + columns */ +{ + ut_ad(dict_sys.locked()); + + if (constraint_name) { + ulint db_len; + + /* Catenate 'databasename/' to the constraint name specified + by the user: we conceive the constraint as belonging to the + same MySQL 'database' as the table itself. We store the name + to foreign->id. */ + + db_len = dict_get_db_name_len(table->name.m_name); + + foreign->id = static_cast<char*>(mem_heap_alloc( + foreign->heap, db_len + strlen(constraint_name) + 2)); + + memcpy(foreign->id, table->name.m_name, db_len); + foreign->id[db_len] = '/'; + strcpy(foreign->id + db_len + 1, constraint_name); + + /* Check if any existing foreign key has the same id, + this is needed only if user supplies the constraint name */ + + if (table->foreign_set.find(foreign) + != table->foreign_set.end()) { + return(false); + } + } + + foreign->foreign_table = table; + foreign->foreign_table_name = mem_heap_strdup( + foreign->heap, table->name.m_name); + dict_mem_foreign_table_name_lookup_set(foreign, TRUE); + + foreign->foreign_index = index; + foreign->n_fields = static_cast<unsigned>(num_field) + & dict_index_t::MAX_N_FIELDS; + + foreign->foreign_col_names = static_cast<const char**>( + mem_heap_alloc(foreign->heap, num_field * sizeof(void*))); + + for (ulint i = 0; i < foreign->n_fields; i++) { + foreign->foreign_col_names[i] = mem_heap_strdup( + foreign->heap, column_names[i]); + } + + foreign->referenced_index = referenced_index; + foreign->referenced_table = referenced_table; + + foreign->referenced_table_name = mem_heap_strdup( + foreign->heap, referenced_table_name); + dict_mem_referenced_table_name_lookup_set(foreign, TRUE); + + foreign->referenced_col_names = static_cast<const char**>( + mem_heap_alloc(foreign->heap, + referenced_num_field * sizeof(void*))); + + for (ulint i = 0; i < foreign->n_fields; i++) { + foreign->referenced_col_names[i] + = mem_heap_strdup(foreign->heap, + referenced_column_names[i]); + } + + return(true); +} + +/*************************************************************//** +Check whether the foreign key options is legit +@return true if it is */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_check_fk_option( +/*=====================*/ + const dict_foreign_t* foreign) /*!< in: foreign key */ +{ + if (!foreign->foreign_index) { + return(true); + } + + if (foreign->type & (DICT_FOREIGN_ON_UPDATE_SET_NULL + | DICT_FOREIGN_ON_DELETE_SET_NULL)) { + + for (ulint j = 0; j < foreign->n_fields; j++) { + if ((dict_index_get_nth_col( + foreign->foreign_index, j)->prtype) + & DATA_NOT_NULL) { + + /* It is not sensible to define + SET NULL if the column is not + allowed to be NULL! */ + return(false); + } + } + } + + return(true); +} + +/*************************************************************//** +Set foreign key options +@return true if successfully set */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_set_foreign_key_option( +/*============================*/ + dict_foreign_t* foreign, /*!< in:InnoDB Foreign key */ + Foreign_key* fk_key) /*!< in: Foreign key info from + MySQL */ +{ + ut_ad(!foreign->type); + + switch (fk_key->delete_opt) { + case FK_OPTION_NO_ACTION: + case FK_OPTION_RESTRICT: + case FK_OPTION_SET_DEFAULT: + foreign->type = DICT_FOREIGN_ON_DELETE_NO_ACTION; + break; + case FK_OPTION_CASCADE: + foreign->type = DICT_FOREIGN_ON_DELETE_CASCADE; + break; + case FK_OPTION_SET_NULL: + foreign->type = DICT_FOREIGN_ON_DELETE_SET_NULL; + break; + case FK_OPTION_UNDEF: + break; + } + + switch (fk_key->update_opt) { + case FK_OPTION_NO_ACTION: + case FK_OPTION_RESTRICT: + case FK_OPTION_SET_DEFAULT: + foreign->type |= DICT_FOREIGN_ON_UPDATE_NO_ACTION; + break; + case FK_OPTION_CASCADE: + foreign->type |= DICT_FOREIGN_ON_UPDATE_CASCADE; + break; + case FK_OPTION_SET_NULL: + foreign->type |= DICT_FOREIGN_ON_UPDATE_SET_NULL; + break; + case FK_OPTION_UNDEF: + break; + } + + return(innobase_check_fk_option(foreign)); +} + +/*******************************************************************//** +Check if a foreign key constraint can make use of an index +that is being created. +@param[in] col_names column names +@param[in] n_cols number of columns +@param[in] keys index information +@param[in] add indexes being created +@return useable index, or NULL if none found */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +const KEY* +innobase_find_equiv_index( + const char*const* col_names, + uint n_cols, + const KEY* keys, + span<uint> add) +{ + for (span<uint>::iterator it = add.begin(), end = add.end(); it != end; + ++it) { + const KEY* key = &keys[*it]; + + if (key->user_defined_key_parts < n_cols + || key->flags & HA_SPATIAL) { +no_match: + continue; + } + + for (uint j = 0; j < n_cols; j++) { + const KEY_PART_INFO& key_part = key->key_part[j]; + uint32 col_len + = key_part.field->pack_length(); + + /* Any index on virtual columns cannot be used + for reference constraint */ + if (!key_part.field->stored_in_db()) { + goto no_match; + } + + /* The MySQL pack length contains 1 or 2 bytes + length field for a true VARCHAR. */ + + if (key_part.field->type() == MYSQL_TYPE_VARCHAR) { + col_len -= static_cast<const Field_varstring*>( + key_part.field)->length_bytes; + } + + if (key_part.length < col_len) { + + /* Column prefix indexes cannot be + used for FOREIGN KEY constraints. */ + goto no_match; + } + + if (innobase_strcasecmp(col_names[j], + key_part.field->field_name.str)) { + /* Name mismatch */ + goto no_match; + } + } + + return(key); + } + + return(NULL); +} + +/*************************************************************//** +Find an index whose first fields are the columns in the array +in the same order and is not marked for deletion +@return matching index, NULL if not found */ +static MY_ATTRIBUTE((nonnull(1,4), warn_unused_result)) +dict_index_t* +innobase_find_fk_index( +/*===================*/ + dict_table_t* table, /*!< in: table */ + const char** col_names, + /*!< in: column names, or NULL + to use table->col_names */ + span<dict_index_t*> drop_index, + /*!< in: indexes to be dropped */ + const char** columns,/*!< in: array of column names */ + ulint n_cols) /*!< in: number of columns */ +{ + dict_index_t* index; + + index = dict_table_get_first_index(table); + + while (index != NULL) { + if (dict_foreign_qualify_index(table, col_names, columns, + n_cols, index, NULL, true, 0, + NULL, NULL, NULL) + && std::find(drop_index.begin(), drop_index.end(), index) + == drop_index.end()) { + return index; + } + + index = dict_table_get_next_index(index); + } + + return(NULL); +} + +/** Check whether given column is a base of stored column. +@param[in] col_name column name +@param[in] table table +@param[in] s_cols list of stored columns +@return true if the given column is a base of stored column,else false. */ +static +bool +innobase_col_check_fk( + const char* col_name, + const dict_table_t* table, + dict_s_col_list* s_cols) +{ + dict_s_col_list::const_iterator it; + + for (it = s_cols->begin(); it != s_cols->end(); ++it) { + for (ulint j = it->num_base; j--; ) { + if (!strcmp(col_name, dict_table_get_col_name( + table, it->base_col[j]->ind))) { + return(true); + } + } + } + + return(false); +} + +/** Check whether the foreign key constraint is on base of any stored columns. +@param[in] foreign Foriegn key constraing information +@param[in] table table to which the foreign key objects +to be added +@param[in] s_cols list of stored column information in the table. +@return true if yes, otherwise false. */ +static +bool +innobase_check_fk_stored( + const dict_foreign_t* foreign, + const dict_table_t* table, + dict_s_col_list* s_cols) +{ + ulint type = foreign->type; + + type &= ~(DICT_FOREIGN_ON_DELETE_NO_ACTION + | DICT_FOREIGN_ON_UPDATE_NO_ACTION); + + if (type == 0 || s_cols == NULL) { + return(false); + } + + for (ulint i = 0; i < foreign->n_fields; i++) { + if (innobase_col_check_fk( + foreign->foreign_col_names[i], table, s_cols)) { + return(true); + } + } + + return(false); +} + +/** Create InnoDB foreign key structure from MySQL alter_info +@param[in] ha_alter_info alter table info +@param[in] table_share TABLE_SHARE +@param[in] table table object +@param[in] col_names column names, or NULL to use +table->col_names +@param[in] drop_index indexes to be dropped +@param[in] n_drop_index size of drop_index +@param[out] add_fk foreign constraint added +@param[out] n_add_fk number of foreign constraints +added +@param[in] trx user transaction +@param[in] s_cols list of stored column information +@retval true if successful +@retval false on error (will call my_error()) */ +static MY_ATTRIBUTE((nonnull(1,2,3,7,8), warn_unused_result)) +bool +innobase_get_foreign_key_info( + Alter_inplace_info* + ha_alter_info, + const TABLE_SHARE* + table_share, + dict_table_t* table, + const char** col_names, + dict_index_t** drop_index, + ulint n_drop_index, + dict_foreign_t**add_fk, + ulint* n_add_fk, + const trx_t* trx, + dict_s_col_list*s_cols) +{ + dict_table_t* referenced_table = NULL; + char* referenced_table_name = NULL; + ulint num_fk = 0; + Alter_info* alter_info = ha_alter_info->alter_info; + const CHARSET_INFO* cs = thd_charset(trx->mysql_thd); + + DBUG_ENTER("innobase_get_foreign_key_info"); + + *n_add_fk = 0; + + for (Key& key : alter_info->key_list) { + if (key.type != Key::FOREIGN_KEY || key.old) { + continue; + } + + const char* column_names[MAX_NUM_FK_COLUMNS]; + dict_index_t* index = NULL; + const char* referenced_column_names[MAX_NUM_FK_COLUMNS]; + dict_index_t* referenced_index = NULL; + ulint num_col = 0; + ulint referenced_num_col = 0; + bool correct_option; + + Foreign_key* fk_key = static_cast<Foreign_key*>(&key); + + if (fk_key->columns.elements > 0) { + ulint i = 0; + + /* Get all the foreign key column info for the + current table */ + for (const Key_part_spec& column : fk_key->columns) { + column_names[i] = column.field_name.str; + ut_ad(i < MAX_NUM_FK_COLUMNS); + i++; + } + + index = innobase_find_fk_index( + table, col_names, + span<dict_index_t*>(drop_index, n_drop_index), + column_names, i); + + /* MySQL would add a index in the creation + list if no such index for foreign table, + so we have to use DBUG_EXECUTE_IF to simulate + the scenario */ + DBUG_EXECUTE_IF("innodb_test_no_foreign_idx", + index = NULL;); + + /* Check whether there exist such + index in the the index create clause */ + if (!index && !innobase_find_equiv_index( + column_names, static_cast<uint>(i), + ha_alter_info->key_info_buffer, + span<uint>(ha_alter_info->index_add_buffer, + ha_alter_info->index_add_count))) { + my_error( + ER_FK_NO_INDEX_CHILD, + MYF(0), + fk_key->name.str + ? fk_key->name.str : "", + table_share->table_name.str); + goto err_exit; + } + + num_col = i; + } + + add_fk[num_fk] = dict_mem_foreign_create(); + + dict_sys.lock(SRW_LOCK_CALL); + + referenced_table_name = dict_get_referenced_table( + table->name.m_name, + LEX_STRING_WITH_LEN(fk_key->ref_db), + LEX_STRING_WITH_LEN(fk_key->ref_table), + &referenced_table, + add_fk[num_fk]->heap, cs); + + /* Test the case when referenced_table failed to + open, if trx->check_foreigns is not set, we should + still be able to add the foreign key */ + DBUG_EXECUTE_IF("innodb_test_open_ref_fail", + referenced_table = NULL;); + + if (!referenced_table && trx->check_foreigns) { + my_error(ER_FK_CANNOT_OPEN_PARENT, + MYF(0), fk_key->ref_table.str); + goto err_exit_unlock; + } + + if (fk_key->ref_columns.elements > 0) { + ulint i = 0; + + for (Key_part_spec &column : fk_key->ref_columns) { + referenced_column_names[i] = + column.field_name.str; + ut_ad(i < MAX_NUM_FK_COLUMNS); + i++; + } + + if (referenced_table) { + referenced_index = + dict_foreign_find_index( + referenced_table, 0, + referenced_column_names, + i, index, + TRUE, FALSE, + NULL, NULL, NULL); + + DBUG_EXECUTE_IF( + "innodb_test_no_reference_idx", + referenced_index = NULL;); + + /* Check whether there exist such + index in the the index create clause */ + if (!referenced_index) { + my_error(ER_FK_NO_INDEX_PARENT, MYF(0), + fk_key->name.str + ? fk_key->name.str : "", + fk_key->ref_table.str); + goto err_exit_unlock; + } + } else { + ut_a(!trx->check_foreigns); + } + + referenced_num_col = i; + } else { + /* Not possible to add a foreign key without a + referenced column */ + my_error(ER_CANNOT_ADD_FOREIGN, MYF(0), + fk_key->ref_table.str); + goto err_exit_unlock; + } + + if (!innobase_init_foreign( + add_fk[num_fk], fk_key->name.str, + table, index, column_names, + num_col, referenced_table_name, + referenced_table, referenced_index, + referenced_column_names, referenced_num_col)) { + my_error( + ER_DUP_CONSTRAINT_NAME, + MYF(0), + "FOREIGN KEY", add_fk[num_fk]->id); + goto err_exit_unlock; + } + + dict_sys.unlock(); + + correct_option = innobase_set_foreign_key_option( + add_fk[num_fk], fk_key); + + DBUG_EXECUTE_IF("innodb_test_wrong_fk_option", + correct_option = false;); + + if (!correct_option) { + my_error(ER_FK_INCORRECT_OPTION, + MYF(0), + table_share->table_name.str, + add_fk[num_fk]->id); + goto err_exit; + } + + if (innobase_check_fk_stored( + add_fk[num_fk], table, s_cols)) { + my_printf_error( + HA_ERR_UNSUPPORTED, + "Cannot add foreign key on the base column " + "of stored column", MYF(0)); + goto err_exit; + } + + num_fk++; + } + + *n_add_fk = num_fk; + + DBUG_RETURN(true); +err_exit_unlock: + dict_sys.unlock(); +err_exit: + for (ulint i = 0; i <= num_fk; i++) { + if (add_fk[i]) { + dict_foreign_free(add_fk[i]); + } + } + + DBUG_RETURN(false); +} + +/*************************************************************//** +Copies an InnoDB column to a MySQL field. This function is +adapted from row_sel_field_store_in_mysql_format(). */ +static +void +innobase_col_to_mysql( +/*==================*/ + const dict_col_t* col, /*!< in: InnoDB column */ + const uchar* data, /*!< in: InnoDB column data */ + ulint len, /*!< in: length of data, in bytes */ + Field* field) /*!< in/out: MySQL field */ +{ + uchar* ptr; + uchar* dest = field->ptr; + ulint flen = field->pack_length(); + + switch (col->mtype) { + case DATA_INT: + ut_ad(len == flen); + + /* Convert integer data from Innobase to little-endian + format, sign bit restored to normal */ + + for (ptr = dest + len; ptr != dest; ) { + *--ptr = *data++; + } + + if (!(col->prtype & DATA_UNSIGNED)) { + ((byte*) dest)[len - 1] ^= 0x80; + } + + break; + + case DATA_VARCHAR: + case DATA_VARMYSQL: + case DATA_BINARY: + field->reset(); + + if (field->type() == MYSQL_TYPE_VARCHAR) { + /* This is a >= 5.0.3 type true VARCHAR. Store the + length of the data to the first byte or the first + two bytes of dest. */ + + dest = row_mysql_store_true_var_len( + dest, len, flen - field->key_length()); + } + + /* Copy the actual data */ + memcpy(dest, data, len); + break; + + case DATA_GEOMETRY: + case DATA_BLOB: + /* Skip MySQL BLOBs when reporting an erroneous row + during index creation or table rebuild. */ + field->set_null(); + break; + +#ifdef UNIV_DEBUG + case DATA_MYSQL: + ut_ad(flen >= len); + ut_ad(col->mbmaxlen >= col->mbminlen); + memcpy(dest, data, len); + break; + + default: + case DATA_SYS_CHILD: + case DATA_SYS: + /* These column types should never be shipped to MySQL. */ + ut_ad(0); + /* fall through */ + case DATA_FLOAT: + case DATA_DOUBLE: + case DATA_DECIMAL: + /* Above are the valid column types for MySQL data. */ + ut_ad(flen == len); + /* fall through */ + case DATA_FIXBINARY: + case DATA_CHAR: + /* We may have flen > len when there is a shorter + prefix on the CHAR and BINARY column. */ + ut_ad(flen >= len); +#else /* UNIV_DEBUG */ + default: +#endif /* UNIV_DEBUG */ + memcpy(dest, data, len); + } +} + +/*************************************************************//** +Copies an InnoDB record to table->record[0]. */ +void +innobase_rec_to_mysql( +/*==================*/ + struct TABLE* table, /*!< in/out: MySQL table */ + const rec_t* rec, /*!< in: record */ + const dict_index_t* index, /*!< in: index */ + const rec_offs* offsets)/*!< in: rec_get_offsets( + rec, index, ...) */ +{ + uint n_fields = table->s->fields; + + ut_ad(n_fields == dict_table_get_n_user_cols(index->table) + - !!(DICT_TF2_FLAG_IS_SET(index->table, + DICT_TF2_FTS_HAS_DOC_ID))); + + for (uint i = 0; i < n_fields; i++) { + Field* field = table->field[i]; + ulint ipos; + ulint ilen; + const uchar* ifield; + ulint prefix_col; + + field->reset(); + + ipos = dict_index_get_nth_col_or_prefix_pos( + index, i, true, false, &prefix_col); + + if (ipos == ULINT_UNDEFINED + || rec_offs_nth_extern(offsets, ipos)) { +null_field: + field->set_null(); + continue; + } + + ifield = rec_get_nth_cfield(rec, index, offsets, ipos, &ilen); + + /* Assign the NULL flag */ + if (ilen == UNIV_SQL_NULL) { + ut_ad(field->real_maybe_null()); + goto null_field; + } + + field->set_notnull(); + + innobase_col_to_mysql( + dict_field_get_col( + dict_index_get_nth_field(index, ipos)), + ifield, ilen, field); + } +} + +/*************************************************************//** +Copies an InnoDB index entry to table->record[0]. +This is used in preparation for print_keydup_error() from +inline add index */ +void +innobase_fields_to_mysql( +/*=====================*/ + struct TABLE* table, /*!< in/out: MySQL table */ + const dict_index_t* index, /*!< in: InnoDB index */ + const dfield_t* fields) /*!< in: InnoDB index fields */ +{ + uint n_fields = table->s->fields; + ulint num_v = 0; + + ut_ad(n_fields == dict_table_get_n_user_cols(index->table) + + dict_table_get_n_v_cols(index->table) + - !!(DICT_TF2_FLAG_IS_SET(index->table, + DICT_TF2_FTS_HAS_DOC_ID))); + + for (uint i = 0; i < n_fields; i++) { + Field* field = table->field[i]; + ulint ipos; + ulint prefix_col; + + field->reset(); + + const bool is_v = !field->stored_in_db(); + const ulint col_n = is_v ? num_v++ : i - num_v; + + ipos = dict_index_get_nth_col_or_prefix_pos( + index, col_n, true, is_v, &prefix_col); + + if (ipos == ULINT_UNDEFINED + || dfield_is_ext(&fields[ipos]) + || dfield_is_null(&fields[ipos])) { + + field->set_null(); + } else { + field->set_notnull(); + + const dfield_t* df = &fields[ipos]; + + innobase_col_to_mysql( + dict_field_get_col( + dict_index_get_nth_field(index, ipos)), + static_cast<const uchar*>(dfield_get_data(df)), + dfield_get_len(df), field); + } + } +} + +/*************************************************************//** +Copies an InnoDB row to table->record[0]. +This is used in preparation for print_keydup_error() from +row_log_table_apply() */ +void +innobase_row_to_mysql( +/*==================*/ + struct TABLE* table, /*!< in/out: MySQL table */ + const dict_table_t* itab, /*!< in: InnoDB table */ + const dtuple_t* row) /*!< in: InnoDB row */ +{ + uint n_fields = table->s->fields; + ulint num_v = 0; + + /* The InnoDB row may contain an extra FTS_DOC_ID column at the end. */ + ut_ad(row->n_fields == dict_table_get_n_cols(itab)); + ut_ad(n_fields == row->n_fields - DATA_N_SYS_COLS + + dict_table_get_n_v_cols(itab) + - !!(DICT_TF2_FLAG_IS_SET(itab, DICT_TF2_FTS_HAS_DOC_ID))); + + for (uint i = 0; i < n_fields; i++) { + Field* field = table->field[i]; + + field->reset(); + + if (!field->stored_in_db()) { + /* Virtual column are not stored in InnoDB table, so + skip it */ + num_v++; + continue; + } + + const dfield_t* df = dtuple_get_nth_field(row, i - num_v); + + if (dfield_is_ext(df) || dfield_is_null(df)) { + field->set_null(); + } else { + field->set_notnull(); + + innobase_col_to_mysql( + dict_table_get_nth_col(itab, i - num_v), + static_cast<const uchar*>(dfield_get_data(df)), + dfield_get_len(df), field); + } + } + if (table->vfield) { + MY_BITMAP* old_read_set = tmp_use_all_columns(table, &table->read_set); + table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_READ); + tmp_restore_column_map(&table->read_set, old_read_set); + } +} + +/*******************************************************************//** +This function checks that index keys are sensible. +@return 0 or error number */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +int +innobase_check_index_keys( +/*======================*/ + const Alter_inplace_info* info, + /*!< in: indexes to be created or dropped */ + const dict_table_t* innodb_table) + /*!< in: Existing indexes */ +{ + for (uint key_num = 0; key_num < info->index_add_count; + key_num++) { + const KEY& key = info->key_info_buffer[ + info->index_add_buffer[key_num]]; + + /* Check that the same index name does not appear + twice in indexes to be created. */ + + for (ulint i = 0; i < key_num; i++) { + const KEY& key2 = info->key_info_buffer[ + info->index_add_buffer[i]]; + + if (0 == strcmp(key.name.str, key2.name.str)) { + my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), + key.name.str); + + return(ER_WRONG_NAME_FOR_INDEX); + } + } + + /* Check that the same index name does not already exist. */ + + const dict_index_t* index; + + for (index = dict_table_get_first_index(innodb_table); + index; index = dict_table_get_next_index(index)) { + + if (index->is_committed() + && !strcmp(key.name.str, index->name)) { + break; + } + } + + /* Now we are in a situation where we have "ADD INDEX x" + and an index by the same name already exists. We have 4 + possible cases: + 1. No further clauses for an index x are given. Should reject + the operation. + 2. "DROP INDEX x" is given. Should allow the operation. + 3. "RENAME INDEX x TO y" is given. Should allow the operation. + 4. "DROP INDEX x, RENAME INDEX x TO y" is given. Should allow + the operation, since no name clash occurs. In this particular + case MySQL cancels the operation without calling InnoDB + methods. */ + + if (index) { + /* If a key by the same name is being created and + dropped, the name clash is OK. */ + for (uint i = 0; i < info->index_drop_count; + i++) { + const KEY* drop_key + = info->index_drop_buffer[i]; + + if (0 == strcmp(key.name.str, + drop_key->name.str)) { + goto name_ok; + } + } + + for (const Alter_inplace_info::Rename_key_pair& pair : + info->rename_keys) { + if (0 == strcmp(key.name.str, + pair.old_key->name.str)) { + goto name_ok; + } + } + + my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), + key.name.str); + return(ER_WRONG_NAME_FOR_INDEX); + } + +name_ok: + for (ulint i = 0; i < key.user_defined_key_parts; i++) { + const KEY_PART_INFO& key_part1 + = key.key_part[i]; + const Field* field + = key_part1.field; + unsigned is_unsigned; + + switch (get_innobase_type_from_mysql_type( + &is_unsigned, field)) { + default: + break; + case DATA_INT: + case DATA_FLOAT: + case DATA_DOUBLE: + case DATA_DECIMAL: + /* Check that MySQL does not try to + create a column prefix index field on + an inappropriate data type. */ + + if (field->type() == MYSQL_TYPE_VARCHAR) { + if (key_part1.length + >= field->pack_length() + - ((Field_varstring*) field) + ->length_bytes) { + break; + } + } else { + if (key_part1.length + >= field->pack_length()) { + break; + } + } + + my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB", + field->field_name.str); + return(ER_WRONG_KEY_COLUMN); + } + + /* Check that the same column does not appear + twice in the index. */ + + for (ulint j = 0; j < i; j++) { + const KEY_PART_INFO& key_part2 + = key.key_part[j]; + + if (key_part1.fieldnr != key_part2.fieldnr) { + continue; + } + + my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB", + field->field_name.str); + return(ER_WRONG_KEY_COLUMN); + } + } + } + + return(0); +} + +/** Create index field definition for key part +@param[in] new_clustered true if alter is generating a new clustered +index +@param[in] altered_table MySQL table that is being altered +@param[in] key_part MySQL key definition +@param[out] index_field index field definition for key_part */ +static MY_ATTRIBUTE((nonnull)) +void +innobase_create_index_field_def( + bool new_clustered, + const TABLE* altered_table, + const KEY_PART_INFO* key_part, + index_field_t* index_field) +{ + const Field* field; + unsigned is_unsigned; + unsigned num_v = 0; + + DBUG_ENTER("innobase_create_index_field_def"); + + field = new_clustered + ? altered_table->field[key_part->fieldnr] + : key_part->field; + + for (ulint i = 0; i < key_part->fieldnr; i++) { + if (!altered_table->field[i]->stored_in_db()) { + num_v++; + } + } + + auto col_type = get_innobase_type_from_mysql_type( + &is_unsigned, field); + + if ((index_field->is_v_col = !field->stored_in_db())) { + index_field->col_no = num_v; + } else { + index_field->col_no = key_part->fieldnr - num_v; + } + + index_field->descending= !!(key_part->key_part_flag & HA_REVERSE_SORT); + + if (DATA_LARGE_MTYPE(col_type) + || (key_part->length < field->pack_length() + && field->type() != MYSQL_TYPE_VARCHAR) + || (field->type() == MYSQL_TYPE_VARCHAR + && key_part->length < field->pack_length() + - ((Field_varstring*) field)->length_bytes)) { + + index_field->prefix_len = key_part->length; + } else { + index_field->prefix_len = 0; + } + + DBUG_VOID_RETURN; +} + +/** Create index definition for key +@param[in] altered_table MySQL table that is being altered +@param[in] keys key definitions +@param[in] key_number MySQL key number +@param[in] new_clustered true if generating a new clustered +index on the table +@param[in] key_clustered true if this is the new clustered index +@param[out] index index definition +@param[in] heap heap where memory is allocated */ +static MY_ATTRIBUTE((nonnull)) +void +innobase_create_index_def( + const TABLE* altered_table, + const KEY* keys, + ulint key_number, + bool new_clustered, + bool key_clustered, + index_def_t* index, + mem_heap_t* heap) +{ + const KEY* key = &keys[key_number]; + ulint i; + ulint n_fields = key->user_defined_key_parts; + + DBUG_ENTER("innobase_create_index_def"); + DBUG_ASSERT(!key_clustered || new_clustered); + + index->fields = static_cast<index_field_t*>( + mem_heap_alloc(heap, n_fields * sizeof *index->fields)); + + index->parser = NULL; + index->key_number = key_number; + index->n_fields = n_fields; + index->name = mem_heap_strdup(heap, key->name.str); + index->rebuild = new_clustered; + + if (key_clustered) { + DBUG_ASSERT(!(key->flags & (HA_FULLTEXT | HA_SPATIAL))); + DBUG_ASSERT(key->flags & HA_NOSAME); + index->ind_type = DICT_CLUSTERED | DICT_UNIQUE; + } else if (key->flags & HA_FULLTEXT) { + DBUG_ASSERT(!(key->flags & (HA_SPATIAL | HA_NOSAME))); + DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK + & ~(HA_FULLTEXT + | HA_PACK_KEY + | HA_BINARY_PACK_KEY))); + index->ind_type = DICT_FTS; + + /* Note: key->parser is only parser name, + we need to get parser from altered_table instead */ + + if (key->flags & HA_USES_PARSER) { + for (ulint j = 0; j < altered_table->s->keys; j++) { + if (!strcmp(altered_table->key_info[j].name.str, + key->name.str)) { + ut_ad(altered_table->key_info[j].flags + & HA_USES_PARSER); + + plugin_ref parser = + altered_table->key_info[j].parser; + index->parser = + static_cast<st_mysql_ftparser*>( + plugin_decl(parser)->info); + + break; + } + } + + DBUG_EXECUTE_IF("fts_instrument_use_default_parser", + index->parser = &fts_default_parser;); + ut_ad(index->parser); + } + } else if (key->flags & HA_SPATIAL) { + DBUG_ASSERT(!(key->flags & HA_NOSAME)); + index->ind_type = DICT_SPATIAL; + ut_ad(n_fields == 1); + ulint num_v = 0; + + /* Need to count the virtual fields before this spatial + indexed field */ + for (ulint i = 0; i < key->key_part->fieldnr; i++) { + num_v += !altered_table->field[i]->stored_in_db(); + } + index->fields[0].col_no = key->key_part[0].fieldnr - num_v; + index->fields[0].prefix_len = 0; + index->fields[0].is_v_col = false; + index->fields[0].descending = false; + + /* Currently, the spatial index cannot be created + on virtual columns. It is blocked in the SQL layer. */ + DBUG_ASSERT(key->key_part[0].field->stored_in_db()); + } else { + index->ind_type = (key->flags & HA_NOSAME) ? DICT_UNIQUE : 0; + } + + if (!(key->flags & HA_SPATIAL)) { + for (i = 0; i < n_fields; i++) { + innobase_create_index_field_def( + new_clustered, altered_table, + &key->key_part[i], &index->fields[i]); + + if (index->fields[i].is_v_col) { + index->ind_type |= DICT_VIRTUAL; + } + } + } + + DBUG_VOID_RETURN; +} + +/*******************************************************************//** +Check whether the table has a unique index with FTS_DOC_ID_INDEX_NAME +on the Doc ID column. +@return the status of the FTS_DOC_ID index */ +enum fts_doc_id_index_enum +innobase_fts_check_doc_id_index( +/*============================*/ + const dict_table_t* table, /*!< in: table definition */ + const TABLE* altered_table, /*!< in: MySQL table + that is being altered */ + ulint* fts_doc_col_no) /*!< out: The column number for + Doc ID, or ULINT_UNDEFINED + if it is being created in + ha_alter_info */ +{ + const dict_index_t* index; + const dict_field_t* field; + + if (altered_table) { + /* Check if a unique index with the name of + FTS_DOC_ID_INDEX_NAME is being created. */ + + const ulint fts_n_uniq= altered_table->versioned() ? 2 : 1; + + for (uint i = 0; i < altered_table->s->keys; i++) { + const KEY& key = altered_table->key_info[i]; + + if (innobase_strcasecmp( + key.name.str, FTS_DOC_ID_INDEX_NAME)) { + continue; + } + + if ((key.flags & HA_NOSAME) + && key.user_defined_key_parts == fts_n_uniq + && !(key.key_part[0].key_part_flag + & HA_REVERSE_SORT) + && !strcmp(key.name.str, FTS_DOC_ID_INDEX_NAME) + && !strcmp(key.key_part[0].field->field_name.str, + FTS_DOC_ID_COL_NAME)) { + if (fts_doc_col_no) { + *fts_doc_col_no = ULINT_UNDEFINED; + } + return(FTS_EXIST_DOC_ID_INDEX); + } else { + return(FTS_INCORRECT_DOC_ID_INDEX); + } + } + } + + if (!table) { + return(FTS_NOT_EXIST_DOC_ID_INDEX); + } + + for (index = dict_table_get_first_index(table); + index; index = dict_table_get_next_index(index)) { + + + /* Check if there exists a unique index with the name of + FTS_DOC_ID_INDEX_NAME and ignore the corrupted index */ + if (index->type & DICT_CORRUPT + || innobase_strcasecmp(index->name, FTS_DOC_ID_INDEX_NAME)) { + continue; + } + + if (!dict_index_is_unique(index) + || dict_index_get_n_unique(index) != table->fts_n_uniq() + || strcmp(index->name, FTS_DOC_ID_INDEX_NAME)) { + return(FTS_INCORRECT_DOC_ID_INDEX); + } + + /* Check whether the index has FTS_DOC_ID as its + first column */ + field = dict_index_get_nth_field(index, 0); + + /* The column would be of a BIGINT data type */ + if (strcmp(field->name, FTS_DOC_ID_COL_NAME) == 0 + && !field->descending + && field->col->mtype == DATA_INT + && field->col->len == 8 + && field->col->prtype & DATA_NOT_NULL + && !field->col->is_virtual()) { + if (fts_doc_col_no) { + *fts_doc_col_no = dict_col_get_no(field->col); + } + return(FTS_EXIST_DOC_ID_INDEX); + } else { + return(FTS_INCORRECT_DOC_ID_INDEX); + } + } + + + /* Not found */ + return(FTS_NOT_EXIST_DOC_ID_INDEX); +} +/*******************************************************************//** +Check whether the table has a unique index with FTS_DOC_ID_INDEX_NAME +on the Doc ID column in MySQL create index definition. +@return FTS_EXIST_DOC_ID_INDEX if there exists the FTS_DOC_ID index, +FTS_INCORRECT_DOC_ID_INDEX if the FTS_DOC_ID index is of wrong format */ +enum fts_doc_id_index_enum +innobase_fts_check_doc_id_index_in_def( +/*===================================*/ + ulint n_key, /*!< in: Number of keys */ + const KEY* key_info) /*!< in: Key definition */ +{ + /* Check whether there is a "FTS_DOC_ID_INDEX" in the to be built index + list */ + const uint fts_n_uniq= key_info->table->versioned() ? 2 : 1; + for (ulint j = 0; j < n_key; j++) { + const KEY* key = &key_info[j]; + + if (innobase_strcasecmp(key->name.str, FTS_DOC_ID_INDEX_NAME)) { + continue; + } + + /* Do a check on FTS DOC ID_INDEX, it must be unique, + named as "FTS_DOC_ID_INDEX" and on column "FTS_DOC_ID" */ + if (!(key->flags & HA_NOSAME) + || key->user_defined_key_parts != fts_n_uniq + || (key->key_part[0].key_part_flag & HA_REVERSE_SORT) + || strcmp(key->name.str, FTS_DOC_ID_INDEX_NAME) + || strcmp(key->key_part[0].field->field_name.str, + FTS_DOC_ID_COL_NAME)) { + return(FTS_INCORRECT_DOC_ID_INDEX); + } + + return(FTS_EXIST_DOC_ID_INDEX); + } + + return(FTS_NOT_EXIST_DOC_ID_INDEX); +} + +/** Create an index table where indexes are ordered as follows: + +IF a new primary key is defined for the table THEN + + 1) New primary key + 2) The remaining keys in key_info + +ELSE + + 1) All new indexes in the order they arrive from MySQL + +ENDIF + +@return key definitions */ +MY_ATTRIBUTE((nonnull, warn_unused_result, malloc)) +inline index_def_t* +ha_innobase_inplace_ctx::create_key_defs( + const Alter_inplace_info* ha_alter_info, + /*!< in: alter operation */ + const TABLE* altered_table, + /*!< in: MySQL table that is being altered */ + ulint& n_fts_add, + /*!< out: number of FTS indexes to be created */ + ulint& fts_doc_id_col, + /*!< in: The column number for Doc ID */ + bool& add_fts_doc_id, + /*!< in: whether we need to add new DOC ID + column for FTS index */ + bool& add_fts_doc_idx, + /*!< in: whether we need to add new DOC ID + index for FTS index */ + const TABLE* table) + /*!< in: MySQL table that is being altered */ +{ + ulint& n_add = num_to_add_index; + const bool got_default_clust = new_table->indexes.start->is_gen_clust(); + + index_def_t* indexdef; + index_def_t* indexdefs; + bool new_primary; + const uint*const add + = ha_alter_info->index_add_buffer; + const KEY*const key_info + = ha_alter_info->key_info_buffer; + + DBUG_ENTER("ha_innobase_inplace_ctx::create_key_defs"); + DBUG_ASSERT(!add_fts_doc_id || add_fts_doc_idx); + DBUG_ASSERT(ha_alter_info->index_add_count == n_add); + + /* If there is a primary key, it is always the first index + defined for the innodb_table. */ + + new_primary = n_add > 0 + && !my_strcasecmp(system_charset_info, + key_info[*add].name.str, "PRIMARY"); + n_fts_add = 0; + + /* If there is a UNIQUE INDEX consisting entirely of NOT NULL + columns and if the index does not contain column prefix(es) + (only prefix/part of the column is indexed), MySQL will treat the + index as a PRIMARY KEY unless the table already has one. */ + + ut_ad(altered_table->s->primary_key == 0 + || altered_table->s->primary_key == MAX_KEY); + + if (got_default_clust && !new_primary) { + new_primary = (altered_table->s->primary_key != MAX_KEY); + } + + const bool rebuild = new_primary || add_fts_doc_id + || innobase_need_rebuild(ha_alter_info, table); + + /* Reserve one more space if new_primary is true, and we might + need to add the FTS_DOC_ID_INDEX */ + indexdef = indexdefs = static_cast<index_def_t*>( + mem_heap_alloc( + heap, sizeof *indexdef + * (ha_alter_info->key_count + + rebuild + + got_default_clust))); + + if (rebuild) { + ulint primary_key_number; + + if (new_primary) { + DBUG_ASSERT(n_add || got_default_clust); + DBUG_ASSERT(n_add || !altered_table->s->primary_key); + primary_key_number = altered_table->s->primary_key; + } else if (got_default_clust) { + /* Create the GEN_CLUST_INDEX */ + index_def_t* index = indexdef++; + + index->fields = NULL; + index->n_fields = 0; + index->ind_type = DICT_CLUSTERED; + index->name = innobase_index_reserve_name; + index->rebuild = true; + index->key_number = ~0U; + primary_key_number = ULINT_UNDEFINED; + goto created_clustered; + } else { + primary_key_number = 0; + } + + /* Create the PRIMARY key index definition */ + innobase_create_index_def( + altered_table, key_info, primary_key_number, + true, true, indexdef++, heap); + +created_clustered: + n_add = 1; + + for (ulint i = 0; i < ha_alter_info->key_count; i++) { + if (i == primary_key_number) { + continue; + } + /* Copy the index definitions. */ + innobase_create_index_def( + altered_table, key_info, i, true, + false, indexdef, heap); + + if (indexdef->ind_type & DICT_FTS) { + n_fts_add++; + } + + indexdef++; + n_add++; + } + + if (n_fts_add > 0) { + ulint num_v = 0; + + if (!add_fts_doc_id + && !innobase_fts_check_doc_id_col( + NULL, altered_table, + &fts_doc_id_col, &num_v)) { + fts_doc_id_col = altered_table->s->fields - num_v; + add_fts_doc_id = true; + } + + if (!add_fts_doc_idx) { + fts_doc_id_index_enum ret; + ulint doc_col_no; + + ret = innobase_fts_check_doc_id_index( + NULL, altered_table, &doc_col_no); + + /* This should have been checked before */ + ut_ad(ret != FTS_INCORRECT_DOC_ID_INDEX); + + if (ret == FTS_NOT_EXIST_DOC_ID_INDEX) { + add_fts_doc_idx = true; + } else { + ut_ad(ret == FTS_EXIST_DOC_ID_INDEX); + ut_ad(doc_col_no == ULINT_UNDEFINED + || doc_col_no == fts_doc_id_col); + } + } + } + } else { + /* Create definitions for added secondary indexes. */ + + for (ulint i = 0; i < n_add; i++) { + innobase_create_index_def( + altered_table, key_info, add[i], + false, false, indexdef, heap); + + if (indexdef->ind_type & DICT_FTS) { + n_fts_add++; + } + + indexdef++; + } + } + + DBUG_ASSERT(indexdefs + n_add == indexdef); + + if (add_fts_doc_idx) { + index_def_t* index = indexdef++; + uint nfields = 1; + + if (altered_table->versioned()) + ++nfields; + index->fields = static_cast<index_field_t*>( + mem_heap_alloc(heap, sizeof(*index->fields) * nfields)); + index->n_fields = nfields; + index->fields[0].col_no = fts_doc_id_col; + index->fields[0].prefix_len = 0; + index->fields[0].descending = false; + index->fields[0].is_v_col = false; + if (nfields == 2) { + index->fields[1].col_no + = altered_table->s->vers.end_fieldno; + index->fields[1].prefix_len = 0; + index->fields[1].descending = false; + index->fields[1].is_v_col = false; + } + index->ind_type = DICT_UNIQUE; + ut_ad(!rebuild + || !add_fts_doc_id + || fts_doc_id_col <= altered_table->s->fields); + + index->name = FTS_DOC_ID_INDEX_NAME; + index->rebuild = rebuild; + + /* TODO: assign a real MySQL key number for this */ + index->key_number = ULINT_UNDEFINED; + n_add++; + } + + DBUG_ASSERT(indexdef > indexdefs); + DBUG_ASSERT((ulint) (indexdef - indexdefs) + <= ha_alter_info->key_count + + add_fts_doc_idx + got_default_clust); + DBUG_ASSERT(ha_alter_info->index_add_count <= n_add); + DBUG_RETURN(indexdefs); +} + +MY_ATTRIBUTE((warn_unused_result)) +bool too_big_key_part_length(size_t max_field_len, const KEY& key) +{ + for (ulint i = 0; i < key.user_defined_key_parts; i++) { + if (key.key_part[i].length > max_field_len) { + return true; + } + } + return false; +} + +/********************************************************************//** +Drop any indexes that we were not able to free previously due to +open table handles. */ +static +void +online_retry_drop_indexes_low( +/*==========================*/ + dict_table_t* table, /*!< in/out: table */ + trx_t* trx) /*!< in/out: transaction */ +{ + ut_ad(dict_sys.locked()); + ut_ad(trx->dict_operation_lock_mode); + ut_ad(trx->dict_operation); + + /* We can have table->n_ref_count > 1, because other threads + may have prebuilt->table pointing to the table. However, these + other threads should be between statements, waiting for the + next statement to execute, or for a meta-data lock. */ + ut_ad(table->get_ref_count() >= 1); + + if (table->drop_aborted) { + row_merge_drop_indexes(trx, table, true); + } +} + +/** After commit, unlock the data dictionary and close any deleted files. +@param deleted handles of deleted files +@param trx committed transaction */ +static void unlock_and_close_files(const std::vector<pfs_os_file_t> &deleted, + trx_t *trx) +{ + row_mysql_unlock_data_dictionary(trx); + for (pfs_os_file_t d : deleted) + os_file_close(d); + log_write_up_to(trx->commit_lsn, true); +} + +/** Commit a DDL transaction and unlink any deleted files. */ +static void commit_unlock_and_unlink(trx_t *trx) +{ + std::vector<pfs_os_file_t> deleted; + trx->commit(deleted); + unlock_and_close_files(deleted, trx); +} + +/** +Drop any indexes that we were not able to free previously due to +open table handles. +@param table InnoDB table +@param thd connection handle +*/ +static void online_retry_drop_indexes(dict_table_t *table, THD *thd) +{ + if (table->drop_aborted) + { + trx_t *trx= innobase_trx_allocate(thd); + + trx_start_for_ddl(trx); + if (lock_sys_tables(trx) == DB_SUCCESS) + { + row_mysql_lock_data_dictionary(trx); + online_retry_drop_indexes_low(table, trx); + commit_unlock_and_unlink(trx); + } + else + trx->commit(); + trx->free(); + } + + ut_d(dict_sys.freeze(SRW_LOCK_CALL)); + ut_d(dict_table_check_for_dup_indexes(table, CHECK_ALL_COMPLETE)); + ut_d(dict_sys.unfreeze()); + ut_ad(!table->drop_aborted); +} + +/** Determines if InnoDB is dropping a foreign key constraint. +@param foreign the constraint +@param drop_fk constraints being dropped +@param n_drop_fk number of constraints that are being dropped +@return whether the constraint is being dropped */ +MY_ATTRIBUTE((pure, nonnull(1), warn_unused_result)) +inline +bool +innobase_dropping_foreign( + const dict_foreign_t* foreign, + dict_foreign_t** drop_fk, + ulint n_drop_fk) +{ + while (n_drop_fk--) { + if (*drop_fk++ == foreign) { + return(true); + } + } + + return(false); +} + +/** Determines if an InnoDB FOREIGN KEY constraint depends on a +column that is being dropped or modified to NOT NULL. +@param user_table InnoDB table as it is before the ALTER operation +@param col_name Name of the column being altered +@param drop_fk constraints being dropped +@param n_drop_fk number of constraints that are being dropped +@param drop true=drop column, false=set NOT NULL +@retval true Not allowed (will call my_error()) +@retval false Allowed +*/ +MY_ATTRIBUTE((pure, nonnull(1,4), warn_unused_result)) +static +bool +innobase_check_foreigns_low( + const dict_table_t* user_table, + dict_foreign_t** drop_fk, + ulint n_drop_fk, + const char* col_name, + bool drop) +{ + dict_foreign_t* foreign; + ut_ad(dict_sys.locked()); + + /* Check if any FOREIGN KEY constraints are defined on this + column. */ + + for (dict_foreign_set::const_iterator it = user_table->foreign_set.begin(); + it != user_table->foreign_set.end(); + ++it) { + + foreign = *it; + + if (!drop && !(foreign->type + & (DICT_FOREIGN_ON_DELETE_SET_NULL + | DICT_FOREIGN_ON_UPDATE_SET_NULL))) { + continue; + } + + if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) { + continue; + } + + for (unsigned f = 0; f < foreign->n_fields; f++) { + if (!strcmp(foreign->foreign_col_names[f], + col_name)) { + my_error(drop + ? ER_FK_COLUMN_CANNOT_DROP + : ER_FK_COLUMN_NOT_NULL, MYF(0), + col_name, foreign->id); + return(true); + } + } + } + + if (!drop) { + /* SET NULL clauses on foreign key constraints of + child tables affect the child tables, not the parent table. + The column can be NOT NULL in the parent table. */ + return(false); + } + + /* Check if any FOREIGN KEY constraints in other tables are + referring to the column that is being dropped. */ + for (dict_foreign_set::const_iterator it + = user_table->referenced_set.begin(); + it != user_table->referenced_set.end(); + ++it) { + + foreign = *it; + + if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) { + continue; + } + + for (unsigned f = 0; f < foreign->n_fields; f++) { + char display_name[FN_REFLEN]; + + if (strcmp(foreign->referenced_col_names[f], + col_name)) { + continue; + } + + char* buf_end = innobase_convert_name( + display_name, (sizeof display_name) - 1, + foreign->foreign_table_name, + strlen(foreign->foreign_table_name), + NULL); + *buf_end = '\0'; + my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD, + MYF(0), col_name, foreign->id, + display_name); + + return(true); + } + } + + return(false); +} + +/** Determines if an InnoDB FOREIGN KEY constraint depends on a +column that is being dropped or modified to NOT NULL. +@param ha_alter_info Data used during in-place alter +@param altered_table MySQL table that is being altered +@param old_table MySQL table as it is before the ALTER operation +@param user_table InnoDB table as it is before the ALTER operation +@param drop_fk constraints being dropped +@param n_drop_fk number of constraints that are being dropped +@retval true Not allowed (will call my_error()) +@retval false Allowed +*/ +MY_ATTRIBUTE((pure, nonnull(1,2,3), warn_unused_result)) +static +bool +innobase_check_foreigns( + Alter_inplace_info* ha_alter_info, + const TABLE* old_table, + const dict_table_t* user_table, + dict_foreign_t** drop_fk, + ulint n_drop_fk) +{ + for (Field** fp = old_table->field; *fp; fp++) { + ut_ad(!(*fp)->real_maybe_null() + == !!((*fp)->flags & NOT_NULL_FLAG)); + + auto end = ha_alter_info->alter_info->create_list.end(); + auto it = std::find_if( + ha_alter_info->alter_info->create_list.begin(), end, + [fp](const Create_field& field) { + return field.field == *fp; + }); + + if (it == end || (it->flags & NOT_NULL_FLAG)) { + if (innobase_check_foreigns_low( + user_table, drop_fk, n_drop_fk, + (*fp)->field_name.str, it == end)) { + return(true); + } + } + } + + return(false); +} + +/** Convert a default value for ADD COLUMN. +@param[in,out] heap Memory heap where allocated +@param[out] dfield InnoDB data field to copy to +@param[in] field MySQL value for the column +@param[in] old_field Old column if altering; NULL for ADD COLUMN +@param[in] comp nonzero if in compact format. */ +static void innobase_build_col_map_add( + mem_heap_t* heap, + dfield_t* dfield, + const Field* field, + const Field* old_field, + ulint comp) +{ + if (old_field && old_field->real_maybe_null() + && field->real_maybe_null()) { + return; + } + + if (field->is_real_null()) { + dfield_set_null(dfield); + return; + } + + const Field& from = old_field ? *old_field : *field; + ulint size = from.pack_length(); + + byte* buf = static_cast<byte*>(mem_heap_alloc(heap, size)); + + row_mysql_store_col_in_innobase_format( + dfield, buf, true, from.ptr, size, comp); +} + +/** Construct the translation table for reordering, dropping or +adding columns. + +@param ha_alter_info Data used during in-place alter +@param altered_table MySQL table that is being altered +@param table MySQL table as it is before the ALTER operation +@param new_table InnoDB table corresponding to MySQL altered_table +@param old_table InnoDB table corresponding to MYSQL table +@param defaults Default values for ADD COLUMN, or NULL if no ADD COLUMN +@param heap Memory heap where allocated +@return array of integers, mapping column numbers in the table +to column numbers in altered_table */ +static MY_ATTRIBUTE((nonnull(1,2,3,4,5,7), warn_unused_result)) +const ulint* +innobase_build_col_map( +/*===================*/ + Alter_inplace_info* ha_alter_info, + const TABLE* altered_table, + const TABLE* table, + dict_table_t* new_table, + const dict_table_t* old_table, + dtuple_t* defaults, + mem_heap_t* heap) +{ + DBUG_ENTER("innobase_build_col_map"); + DBUG_ASSERT(altered_table != table); + DBUG_ASSERT(new_table != old_table); + DBUG_ASSERT(dict_table_get_n_cols(new_table) + + dict_table_get_n_v_cols(new_table) + >= altered_table->s->fields + DATA_N_SYS_COLS); + DBUG_ASSERT(dict_table_get_n_cols(old_table) + + dict_table_get_n_v_cols(old_table) + >= table->s->fields + DATA_N_SYS_COLS + || ha_innobase::omits_virtual_cols(*table->s)); + DBUG_ASSERT(!!defaults == !!(ha_alter_info->handler_flags + & INNOBASE_DEFAULTS)); + DBUG_ASSERT(!defaults || dtuple_get_n_fields(defaults) + == dict_table_get_n_cols(new_table)); + + const uint old_n_v_cols = uint(table->s->fields + - table->s->stored_fields); + DBUG_ASSERT(old_n_v_cols == old_table->n_v_cols + || table->s->frm_version < FRM_VER_EXPRESSSIONS); + DBUG_ASSERT(!old_n_v_cols || table->s->virtual_fields); + + ulint* col_map = static_cast<ulint*>( + mem_heap_alloc( + heap, (size_t(old_table->n_cols) + old_n_v_cols) + * sizeof *col_map)); + + uint i = 0; + uint num_v = 0; + + /* Any dropped columns will map to ULINT_UNDEFINED. */ + for (uint old_i = 0; old_i + DATA_N_SYS_COLS < old_table->n_cols; + old_i++) { + col_map[old_i] = ULINT_UNDEFINED; + } + + for (uint old_i = 0; old_i < old_n_v_cols; old_i++) { + col_map[old_i + old_table->n_cols] = ULINT_UNDEFINED; + } + + const bool omits_virtual = ha_innobase::omits_virtual_cols(*table->s); + + for (const Create_field& new_field : + ha_alter_info->alter_info->create_list) { + bool is_v = !new_field.stored_in_db(); + ulint num_old_v = 0; + + for (uint old_i = 0; table->field[old_i]; old_i++) { + const Field* field = table->field[old_i]; + if (!field->stored_in_db()) { + if (is_v && new_field.field == field) { + if (!omits_virtual) { + col_map[old_table->n_cols + + num_v] + = num_old_v; + } + num_old_v++; + goto found_col; + } + num_old_v++; + continue; + } + + if (new_field.field == field) { + + const Field* altered_field = + altered_table->field[i + num_v]; + + if (defaults) { + innobase_build_col_map_add( + heap, + dtuple_get_nth_field( + defaults, i), + altered_field, + field, + dict_table_is_comp( + new_table)); + } + + col_map[old_i - num_old_v] = i; + if (!old_table->versioned() + || !altered_table->versioned()) { + } else if (old_i == old_table->vers_start) { + new_table->vers_start = (i + num_v) + & dict_index_t::MAX_N_FIELDS; + } else if (old_i == old_table->vers_end) { + new_table->vers_end = (i + num_v) + & dict_index_t::MAX_N_FIELDS; + } + goto found_col; + } + } + + if (!is_v) { + innobase_build_col_map_add( + heap, dtuple_get_nth_field(defaults, i), + altered_table->field[i + num_v], + NULL, + dict_table_is_comp(new_table)); + } +found_col: + if (is_v) { + num_v++; + } else { + i++; + } + } + + DBUG_ASSERT(i == altered_table->s->fields - num_v); + + i = table->s->fields - old_n_v_cols; + + /* Add the InnoDB hidden FTS_DOC_ID column, if any. */ + if (i + DATA_N_SYS_COLS < old_table->n_cols) { + /* There should be exactly one extra field, + the FTS_DOC_ID. */ + DBUG_ASSERT(DICT_TF2_FLAG_IS_SET(old_table, + DICT_TF2_FTS_HAS_DOC_ID)); + DBUG_ASSERT(i + DATA_N_SYS_COLS + 1 == old_table->n_cols); + DBUG_ASSERT(!strcmp(dict_table_get_col_name( + old_table, i), + FTS_DOC_ID_COL_NAME)); + if (altered_table->s->fields + DATA_N_SYS_COLS + - new_table->n_v_cols + < new_table->n_cols) { + DBUG_ASSERT(DICT_TF2_FLAG_IS_SET( + new_table, + DICT_TF2_FTS_HAS_DOC_ID)); + DBUG_ASSERT(altered_table->s->fields + + DATA_N_SYS_COLS + 1 + == static_cast<ulint>( + new_table->n_cols + + new_table->n_v_cols)); + col_map[i] = altered_table->s->fields + - new_table->n_v_cols; + } else { + DBUG_ASSERT(!DICT_TF2_FLAG_IS_SET( + new_table, + DICT_TF2_FTS_HAS_DOC_ID)); + col_map[i] = ULINT_UNDEFINED; + } + + i++; + } else { + DBUG_ASSERT(!DICT_TF2_FLAG_IS_SET( + old_table, + DICT_TF2_FTS_HAS_DOC_ID)); + } + + for (; i < old_table->n_cols; i++) { + col_map[i] = i + new_table->n_cols - old_table->n_cols; + } + + DBUG_RETURN(col_map); +} + +/** Get the new non-virtual column names if any columns were renamed +@param ha_alter_info Data used during in-place alter +@param altered_table MySQL table that is being altered +@param table MySQL table as it is before the ALTER operation +@param user_table InnoDB table as it is before the ALTER operation +@param heap Memory heap for the allocation +@return array of new column names in rebuilt_table, or NULL if not renamed */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +const char** +innobase_get_col_names( + Alter_inplace_info* ha_alter_info, + const TABLE* altered_table, + const TABLE* table, + const dict_table_t* user_table, + mem_heap_t* heap) +{ + const char** cols; + uint i; + + DBUG_ENTER("innobase_get_col_names"); + DBUG_ASSERT(user_table->n_t_def > table->s->fields); + DBUG_ASSERT(ha_alter_info->handler_flags + & ALTER_COLUMN_NAME); + + cols = static_cast<const char**>( + mem_heap_zalloc(heap, user_table->n_def * sizeof *cols)); + + i = 0; + for (const Create_field& new_field : + ha_alter_info->alter_info->create_list) { + ulint num_v = 0; + DBUG_ASSERT(i < altered_table->s->fields); + + if (!new_field.stored_in_db()) { + continue; + } + + for (uint old_i = 0; table->field[old_i]; old_i++) { + num_v += !table->field[old_i]->stored_in_db(); + + if (new_field.field == table->field[old_i]) { + cols[old_i - num_v] = new_field.field_name.str; + break; + } + } + + i++; + } + + /* Copy the internal column names. */ + i = table->s->fields - user_table->n_v_def; + cols[i] = dict_table_get_col_name(user_table, i); + + while (++i < user_table->n_def) { + cols[i] = cols[i - 1] + strlen(cols[i - 1]) + 1; + } + + DBUG_RETURN(cols); +} + +/** Check whether the column prefix is increased, decreased, or unchanged. +@param[in] new_prefix_len new prefix length +@param[in] old_prefix_len new prefix length +@retval 1 prefix is increased +@retval 0 prefix is unchanged +@retval -1 prefix is decreased */ +static inline +lint +innobase_pk_col_prefix_compare( + ulint new_prefix_len, + ulint old_prefix_len) +{ + ut_ad(new_prefix_len < COMPRESSED_REC_MAX_DATA_SIZE); + ut_ad(old_prefix_len < COMPRESSED_REC_MAX_DATA_SIZE); + + if (new_prefix_len == old_prefix_len) { + return(0); + } + + if (new_prefix_len == 0) { + new_prefix_len = ULINT_MAX; + } + + if (old_prefix_len == 0) { + old_prefix_len = ULINT_MAX; + } + + if (new_prefix_len > old_prefix_len) { + return(1); + } else { + return(-1); + } +} + +/** Check whether the column is existing in old table. +@param[in] new_col_no new column no +@param[in] col_map mapping of old column numbers to new ones +@param[in] col_map_size the column map size +@return true if the column is existing, otherwise false. */ +static inline +bool +innobase_pk_col_is_existing( + const ulint new_col_no, + const ulint* col_map, + const ulint col_map_size) +{ + for (ulint i = 0; i < col_map_size; i++) { + if (col_map[i] == new_col_no) { + return(true); + } + } + + return(false); +} + +/** Determine whether both the indexes have same set of primary key +fields arranged in the same order. + +Rules when we cannot skip sorting: +(1) Removing existing PK columns somewhere else than at the end of the PK; +(2) Adding existing columns to the PK, except at the end of the PK when no +columns are removed from the PK; +(3) Changing the order of existing PK columns; +(4) Decreasing the prefix length just like removing existing PK columns +follows rule(1), Increasing the prefix length just like adding existing +PK columns follows rule(2); +(5) Changing the ASC/DESC attribute of the existing PK columns. +@param[in] col_map mapping of old column numbers to new ones +@param[in] ha_alter_info Data used during in-place alter +@param[in] old_clust_index index to be compared +@param[in] new_clust_index index to be compared +@retval true if both indexes have same order. +@retval false. */ +static MY_ATTRIBUTE((warn_unused_result)) +bool +innobase_pk_order_preserved( + const ulint* col_map, + const dict_index_t* old_clust_index, + const dict_index_t* new_clust_index) +{ + ulint old_n_uniq + = dict_index_get_n_ordering_defined_by_user( + old_clust_index); + ulint new_n_uniq + = dict_index_get_n_ordering_defined_by_user( + new_clust_index); + + ut_ad(dict_index_is_clust(old_clust_index)); + ut_ad(dict_index_is_clust(new_clust_index)); + ut_ad(old_clust_index->table != new_clust_index->table); + ut_ad(col_map != NULL); + + if (old_n_uniq == 0) { + /* There was no PRIMARY KEY in the table. + If there is no PRIMARY KEY after the ALTER either, + no sorting is needed. */ + return(new_n_uniq == old_n_uniq); + } + + /* DROP PRIMARY KEY is only allowed in combination with + ADD PRIMARY KEY. */ + ut_ad(new_n_uniq > 0); + + /* The order of the last processed new_clust_index key field, + not counting ADD COLUMN, which are constant. */ + lint last_field_order = -1; + ulint existing_field_count = 0; + ulint old_n_cols = dict_table_get_n_cols(old_clust_index->table); + for (ulint new_field = 0; new_field < new_n_uniq; new_field++) { + ulint new_col_no = + new_clust_index->fields[new_field].col->ind; + + /* Check if there is a match in old primary key. */ + ulint old_field = 0; + while (old_field < old_n_uniq) { + ulint old_col_no = + old_clust_index->fields[old_field].col->ind; + + if (col_map[old_col_no] == new_col_no) { + break; + } + + old_field++; + } + + /* The order of key field in the new primary key. + 1. old PK column: idx in old primary key + 2. existing column: old_n_uniq + sequence no + 3. newly added column: no order */ + lint new_field_order; + const bool old_pk_column = old_field < old_n_uniq; + + if (old_pk_column) { + new_field_order = lint(old_field); + } else if (innobase_pk_col_is_existing(new_col_no, col_map, + old_n_cols) + || new_clust_index->table->persistent_autoinc + == new_field + 1) { + /* Adding an existing column or an AUTO_INCREMENT + column may change the existing ordering. */ + new_field_order = lint(old_n_uniq + + existing_field_count++); + } else { + /* Skip newly added column. */ + continue; + } + + if (last_field_order + 1 != new_field_order) { + /* Old PK order is not kept, or existing column + is not added at the end of old PK. */ + return(false); + } + + last_field_order = new_field_order; + + if (!old_pk_column) { + continue; + } + + const dict_field_t &of = old_clust_index->fields[old_field]; + const dict_field_t &nf = new_clust_index->fields[new_field]; + + if (of.descending != nf.descending) { + return false; + } + + /* Check prefix length change. */ + const lint prefix_change = innobase_pk_col_prefix_compare( + nf.prefix_len, of.prefix_len); + + if (prefix_change < 0) { + /* If a column's prefix length is decreased, it should + be the last old PK column in new PK. + Note: we set last_field_order to -2, so that if there + are any old PK colmns or existing columns after it in + new PK, the comparison to new_field_order will fail in + the next round.*/ + last_field_order = -2; + } else if (prefix_change > 0) { + /* If a column's prefix length is increased, it should + be the last PK column in old PK. */ + if (old_field != old_n_uniq - 1) { + return(false); + } + } + } + + return(true); +} + +/** Update the mtype from DATA_BLOB to DATA_GEOMETRY for a specified +GIS column of a table. This is used when we want to create spatial index +on legacy GIS columns coming from 5.6, where we store GIS data as DATA_BLOB +in innodb layer. +@param[in] table_id table id +@param[in] col_name column name +@param[in] trx data dictionary transaction +@retval true Failure +@retval false Success */ +static +bool +innobase_update_gis_column_type( + table_id_t table_id, + const char* col_name, + trx_t* trx) +{ + pars_info_t* info; + dberr_t error; + + DBUG_ENTER("innobase_update_gis_column_type"); + + DBUG_ASSERT(trx->dict_operation); + ut_ad(trx->dict_operation_lock_mode); + ut_ad(dict_sys.locked()); + + info = pars_info_create(); + + pars_info_add_ull_literal(info, "tableid", table_id); + pars_info_add_str_literal(info, "name", col_name); + pars_info_add_int4_literal(info, "mtype", DATA_GEOMETRY); + + trx->op_info = "update column type to DATA_GEOMETRY"; + + error = que_eval_sql( + info, + "PROCEDURE UPDATE_SYS_COLUMNS_PROC () IS\n" + "BEGIN\n" + "UPDATE SYS_COLUMNS SET MTYPE=:mtype\n" + "WHERE TABLE_ID=:tableid AND NAME=:name;\n" + "END;\n", trx); + + trx->error_state = DB_SUCCESS; + trx->op_info = ""; + + DBUG_RETURN(error != DB_SUCCESS); +} + +/** Check if we are creating spatial indexes on GIS columns, which are +legacy columns from earlier MySQL, such as 5.6. If so, we have to update +the mtypes of the old GIS columns to DATA_GEOMETRY. +In 5.6, we store GIS columns as DATA_BLOB in InnoDB layer, it will introduce +confusion when we run latest server on older data. That's why we need to +do the upgrade. +@param[in] ha_alter_info Data used during in-place alter +@param[in] table Table on which we want to add indexes +@param[in] trx Transaction +@return DB_SUCCESS if update successfully or no columns need to be updated, +otherwise DB_ERROR, which means we can't update the mtype for some +column, and creating spatial index on it should be dangerous */ +static +dberr_t +innobase_check_gis_columns( + Alter_inplace_info* ha_alter_info, + dict_table_t* table, + trx_t* trx) +{ + DBUG_ENTER("innobase_check_gis_columns"); + + for (uint key_num = 0; + key_num < ha_alter_info->index_add_count; + key_num++) { + + const KEY& key = ha_alter_info->key_info_buffer[ + ha_alter_info->index_add_buffer[key_num]]; + + if (!(key.flags & HA_SPATIAL)) { + continue; + } + + ut_ad(key.user_defined_key_parts == 1); + const KEY_PART_INFO& key_part = key.key_part[0]; + + /* Does not support spatial index on virtual columns */ + if (!key_part.field->stored_in_db()) { + DBUG_RETURN(DB_UNSUPPORTED); + } + + ulint col_nr = dict_table_has_column( + table, + key_part.field->field_name.str, + key_part.fieldnr); + ut_ad(col_nr != table->n_def); + dict_col_t* col = &table->cols[col_nr]; + + if (col->mtype != DATA_BLOB) { + ut_ad(DATA_GEOMETRY_MTYPE(col->mtype)); + continue; + } + + const char* col_name = dict_table_get_col_name( + table, col_nr); + + if (innobase_update_gis_column_type( + table->id, col_name, trx)) { + + DBUG_RETURN(DB_ERROR); + } else { + col->mtype = DATA_GEOMETRY; + + ib::info() << "Updated mtype of column" << col_name + << " in table " << table->name + << ", whose id is " << table->id + << " to DATA_GEOMETRY"; + } + } + + DBUG_RETURN(DB_SUCCESS); +} + +/** Collect virtual column info for its addition +@param[in] ha_alter_info Data used during in-place alter +@param[in] altered_table MySQL table that is being altered to +@param[in] table MySQL table as it is before the ALTER operation +@retval true Failure +@retval false Success */ +static +bool +prepare_inplace_add_virtual( + Alter_inplace_info* ha_alter_info, + const TABLE* altered_table, + const TABLE* table) +{ + ha_innobase_inplace_ctx* ctx; + uint16_t i = 0; + + ctx = static_cast<ha_innobase_inplace_ctx*> + (ha_alter_info->handler_ctx); + + unsigned j = altered_table->s->virtual_fields + ctx->num_to_drop_vcol; + + ctx->add_vcol = static_cast<dict_v_col_t*>( + mem_heap_zalloc(ctx->heap, j * sizeof *ctx->add_vcol)); + ctx->add_vcol_name = static_cast<const char**>( + mem_heap_alloc(ctx->heap, j * sizeof *ctx->add_vcol_name)); + + j = 0; + + for (const Create_field& new_field : + ha_alter_info->alter_info->create_list) { + const Field* field = altered_table->field[i++]; + + if (new_field.field || field->stored_in_db()) { + continue; + } + + unsigned is_unsigned; + auto col_type = get_innobase_type_from_mysql_type( + &is_unsigned, field); + + auto col_len = field->pack_length(); + unsigned field_type = field->type() | is_unsigned; + + if (!field->real_maybe_null()) { + field_type |= DATA_NOT_NULL; + } + + if (field->binary()) { + field_type |= DATA_BINARY_TYPE; + } + + unsigned charset_no; + + if (dtype_is_string_type(col_type)) { + charset_no = field->charset()->number; + + DBUG_EXECUTE_IF( + "ib_alter_add_virtual_fail", + charset_no += MAX_CHAR_COLL_NUM;); + + if (charset_no > MAX_CHAR_COLL_NUM) { + my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB", + field->field_name.str); + return(true); + } + } else { + charset_no = 0; + } + + if (field->type() == MYSQL_TYPE_VARCHAR) { + uint32 length_bytes + = static_cast<const Field_varstring*>( + field)->length_bytes; + + col_len -= length_bytes; + + if (length_bytes == 2) { + field_type |= DATA_LONG_TRUE_VARCHAR; + } + } + + new (&ctx->add_vcol[j]) dict_v_col_t(); + ctx->add_vcol[j].m_col.prtype = dtype_form_prtype( + field_type, charset_no); + + ctx->add_vcol[j].m_col.prtype |= DATA_VIRTUAL; + + ctx->add_vcol[j].m_col.mtype = col_type; + + ctx->add_vcol[j].m_col.len = static_cast<uint16_t>(col_len); + + ctx->add_vcol[j].m_col.ind = (i - 1) + & dict_index_t::MAX_N_FIELDS; + ctx->add_vcol[j].num_base = 0; + ctx->add_vcol_name[j] = field->field_name.str; + ctx->add_vcol[j].base_col = NULL; + ctx->add_vcol[j].v_pos = (ctx->old_table->n_v_cols + - ctx->num_to_drop_vcol + j) + & dict_index_t::MAX_N_FIELDS; + + /* MDEV-17468: Do this on ctx->instant_table later */ + innodb_base_col_setup(ctx->old_table, field, &ctx->add_vcol[j]); + j++; + } + + ctx->num_to_add_vcol = j; + return(false); +} + +/** Collect virtual column info for its addition +@param[in] ha_alter_info Data used during in-place alter +@param[in] table MySQL table as it is before the ALTER operation +@retval true Failure +@retval false Success */ +static +bool +prepare_inplace_drop_virtual( + Alter_inplace_info* ha_alter_info, + const TABLE* table) +{ + ha_innobase_inplace_ctx* ctx; + unsigned i = 0, j = 0; + + ctx = static_cast<ha_innobase_inplace_ctx*> + (ha_alter_info->handler_ctx); + + ctx->num_to_drop_vcol = 0; + for (i = 0; table->field[i]; i++) { + const Field* field = table->field[i]; + if (field->flags & FIELD_IS_DROPPED && !field->stored_in_db()) { + ctx->num_to_drop_vcol++; + } + } + + ctx->drop_vcol = static_cast<dict_v_col_t*>( + mem_heap_alloc(ctx->heap, ctx->num_to_drop_vcol + * sizeof *ctx->drop_vcol)); + ctx->drop_vcol_name = static_cast<const char**>( + mem_heap_alloc(ctx->heap, ctx->num_to_drop_vcol + * sizeof *ctx->drop_vcol_name)); + + for (i = 0; table->field[i]; i++) { + Field *field = table->field[i]; + if (!(field->flags & FIELD_IS_DROPPED) || field->stored_in_db()) { + continue; + } + + unsigned is_unsigned; + + auto col_type = get_innobase_type_from_mysql_type( + &is_unsigned, field); + + auto col_len = field->pack_length(); + unsigned field_type = field->type() | is_unsigned; + + if (!field->real_maybe_null()) { + field_type |= DATA_NOT_NULL; + } + + if (field->binary()) { + field_type |= DATA_BINARY_TYPE; + } + + unsigned charset_no = 0; + + if (dtype_is_string_type(col_type)) { + charset_no = field->charset()->number; + + DBUG_EXECUTE_IF( + "ib_alter_add_virtual_fail", + charset_no += MAX_CHAR_COLL_NUM;); + + if (charset_no > MAX_CHAR_COLL_NUM) { + my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB", + field->field_name.str); + return(true); + } + } else { + charset_no = 0; + } + + if (field->type() == MYSQL_TYPE_VARCHAR) { + uint32 length_bytes + = static_cast<const Field_varstring*>( + field)->length_bytes; + + col_len -= length_bytes; + + if (length_bytes == 2) { + field_type |= DATA_LONG_TRUE_VARCHAR; + } + } + + + ctx->drop_vcol[j].m_col.prtype = dtype_form_prtype( + field_type, charset_no); + + ctx->drop_vcol[j].m_col.prtype |= DATA_VIRTUAL; + + ctx->drop_vcol[j].m_col.mtype = col_type; + + ctx->drop_vcol[j].m_col.len = static_cast<uint16_t>(col_len); + + ctx->drop_vcol[j].m_col.ind = i & dict_index_t::MAX_N_FIELDS; + + ctx->drop_vcol_name[j] = field->field_name.str; + + dict_v_col_t* v_col = dict_table_get_nth_v_col_mysql( + ctx->old_table, i); + ctx->drop_vcol[j].v_pos = v_col->v_pos; + j++; + } + + return(false); +} + +/** Insert a new record to INNODB SYS_VIRTUAL +@param[in] table InnoDB table +@param[in] pos virtual column column no +@param[in] base_pos base column pos +@param[in] trx transaction +@retval false on success +@retval true on failure (my_error() will have been called) */ +static bool innobase_insert_sys_virtual( + const dict_table_t* table, + ulint pos, + ulint base_pos, + trx_t* trx) +{ + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "id", table->id); + pars_info_add_int4_literal(info, "pos", pos); + pars_info_add_int4_literal(info, "base_pos", base_pos); + + if (DB_SUCCESS != que_eval_sql( + info, + "PROCEDURE P () IS\n" + "BEGIN\n" + "INSERT INTO SYS_VIRTUAL VALUES (:id, :pos, :base_pos);\n" + "END;\n", trx)) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: ADD COLUMN...VIRTUAL"); + return true; + } + + return false; +} + +/** Insert a record to the SYS_COLUMNS dictionary table. +@param[in] table_id table id +@param[in] pos position of the column +@param[in] field_name field name +@param[in] mtype main type +@param[in] prtype precise type +@param[in] len fixed length in bytes, or 0 +@param[in] n_base number of base columns of virtual columns, or 0 +@param[in] update whether to update instead of inserting +@retval false on success +@retval true on failure (my_error() will have been called) */ +static bool innodb_insert_sys_columns( + table_id_t table_id, + ulint pos, + const char* field_name, + ulint mtype, + ulint prtype, + ulint len, + ulint n_base, + trx_t* trx, + bool update = false) +{ + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "id", table_id); + pars_info_add_int4_literal(info, "pos", pos); + pars_info_add_str_literal(info, "name", field_name); + pars_info_add_int4_literal(info, "mtype", mtype); + pars_info_add_int4_literal(info, "prtype", prtype); + pars_info_add_int4_literal(info, "len", len); + pars_info_add_int4_literal(info, "base", n_base); + + if (update) { + if (DB_SUCCESS != que_eval_sql( + info, + "PROCEDURE UPD_COL () IS\n" + "BEGIN\n" + "UPDATE SYS_COLUMNS SET\n" + "NAME=:name, MTYPE=:mtype, PRTYPE=:prtype, " + "LEN=:len, PREC=:base\n" + "WHERE TABLE_ID=:id AND POS=:pos;\n" + "END;\n", trx)) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: Updating SYS_COLUMNS failed"); + return true; + } + + return false; + } + + if (DB_SUCCESS != que_eval_sql( + info, + "PROCEDURE ADD_COL () IS\n" + "BEGIN\n" + "INSERT INTO SYS_COLUMNS VALUES" + "(:id,:pos,:name,:mtype,:prtype,:len,:base);\n" + "END;\n", trx)) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: Insert into SYS_COLUMNS failed"); + return true; + } + + return false; +} + +/** Update INNODB SYS_COLUMNS on new virtual columns +@param[in] table InnoDB table +@param[in] col_name column name +@param[in] vcol virtual column +@param[in] trx transaction +@retval false on success +@retval true on failure (my_error() will have been called) */ +static bool innobase_add_one_virtual( + const dict_table_t* table, + const char* col_name, + dict_v_col_t* vcol, + trx_t* trx) +{ + ulint pos = dict_create_v_col_pos(vcol->v_pos, + vcol->m_col.ind); + + if (innodb_insert_sys_columns(table->id, pos, col_name, + vcol->m_col.mtype, vcol->m_col.prtype, + vcol->m_col.len, vcol->num_base, trx)) { + return true; + } + + for (unsigned i = 0; i < vcol->num_base; i++) { + if (innobase_insert_sys_virtual( + table, pos, vcol->base_col[i]->ind, trx)) { + return true; + } + } + + return false; +} + +/** Update SYS_TABLES.N_COLS in the data dictionary. +@param[in] user_table InnoDB table +@param[in] n the new value of SYS_TABLES.N_COLS +@param[in] trx transaction +@return whether the operation failed */ +static bool innodb_update_cols(const dict_table_t* table, ulint n, trx_t* trx) +{ + pars_info_t* info = pars_info_create(); + + pars_info_add_int4_literal(info, "n", n); + pars_info_add_ull_literal(info, "id", table->id); + + if (DB_SUCCESS != que_eval_sql(info, + "PROCEDURE UPDATE_N_COLS () IS\n" + "BEGIN\n" + "UPDATE SYS_TABLES SET N_COLS = :n" + " WHERE ID = :id;\n" + "END;\n", trx)) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: Updating SYS_TABLES.N_COLS failed"); + return true; + } + + return false; +} + +/** Update system table for adding virtual column(s) +@param[in] ha_alter_info Data used during in-place alter +@param[in] user_table InnoDB table +@param[in] trx transaction +@retval true Failure +@retval false Success */ +static +bool +innobase_add_virtual_try( + const Alter_inplace_info* ha_alter_info, + const dict_table_t* user_table, + trx_t* trx) +{ + ha_innobase_inplace_ctx* ctx = static_cast<ha_innobase_inplace_ctx*>( + ha_alter_info->handler_ctx); + + for (ulint i = 0; i < ctx->num_to_add_vcol; i++) { + if (innobase_add_one_virtual( + user_table, ctx->add_vcol_name[i], + &ctx->add_vcol[i], trx)) { + return true; + } + } + + return false; +} + +/** Delete metadata from SYS_COLUMNS and SYS_VIRTUAL. +@param[in] id table id +@param[in] pos first SYS_COLUMNS.POS +@param[in,out] trx data dictionary transaction +@retval true Failure +@retval false Success. */ +static bool innobase_instant_drop_cols(table_id_t id, ulint pos, trx_t* trx) +{ + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "id", id); + pars_info_add_int4_literal(info, "pos", pos); + + dberr_t err = que_eval_sql( + info, + "PROCEDURE DELETE_COL () IS\n" + "BEGIN\n" + "DELETE FROM SYS_COLUMNS WHERE\n" + "TABLE_ID = :id AND POS >= :pos;\n" + "DELETE FROM SYS_VIRTUAL WHERE TABLE_ID = :id;\n" + "END;\n", trx); + if (err != DB_SUCCESS) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: DELETE from SYS_COLUMNS/SYS_VIRTUAL failed"); + return true; + } + + return false; +} + +/** Update INNODB SYS_COLUMNS on new virtual column's position +@param[in] table InnoDB table +@param[in] old_pos old position +@param[in] new_pos new position +@param[in] trx transaction +@return DB_SUCCESS if successful, otherwise error code */ +static +dberr_t +innobase_update_v_pos_sys_columns( + const dict_table_t* table, + ulint old_pos, + ulint new_pos, + trx_t* trx) +{ + pars_info_t* info = pars_info_create(); + + pars_info_add_int4_literal(info, "pos", old_pos); + pars_info_add_int4_literal(info, "val", new_pos); + pars_info_add_ull_literal(info, "id", table->id); + + dberr_t error = que_eval_sql( + info, + "PROCEDURE P () IS\n" + "BEGIN\n" + "UPDATE SYS_COLUMNS\n" + "SET POS = :val\n" + "WHERE POS = :pos\n" + "AND TABLE_ID = :id;\n" + "END;\n", trx); + + return(error); +} + +/** Update INNODB SYS_VIRTUAL table with new virtual column position +@param[in] table InnoDB table +@param[in] old_pos old position +@param[in] new_pos new position +@param[in] trx transaction +@return DB_SUCCESS if successful, otherwise error code */ +static +dberr_t +innobase_update_v_pos_sys_virtual( + const dict_table_t* table, + ulint old_pos, + ulint new_pos, + trx_t* trx) +{ + pars_info_t* info = pars_info_create(); + + pars_info_add_int4_literal(info, "pos", old_pos); + pars_info_add_int4_literal(info, "val", new_pos); + pars_info_add_ull_literal(info, "id", table->id); + + dberr_t error = que_eval_sql( + info, + "PROCEDURE P () IS\n" + "BEGIN\n" + "UPDATE SYS_VIRTUAL\n" + "SET POS = :val\n" + "WHERE POS = :pos\n" + "AND TABLE_ID = :id;\n" + "END;\n", trx); + + return(error); +} + +/** Update InnoDB system tables on dropping a virtual column +@param[in] table InnoDB table +@param[in] col_name column name of the dropping column +@param[in] drop_col col information for the dropping column +@param[in] n_prev_dropped number of previously dropped columns in the + same alter clause +@param[in] trx transaction +@return DB_SUCCESS if successful, otherwise error code */ +static +dberr_t +innobase_drop_one_virtual_sys_columns( + const dict_table_t* table, + const char* col_name, + dict_col_t* drop_col, + ulint n_prev_dropped, + trx_t* trx) +{ + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "id", table->id); + + pars_info_add_str_literal(info, "name", col_name); + + dberr_t error = que_eval_sql( + info, + "PROCEDURE P () IS\n" + "BEGIN\n" + "DELETE FROM SYS_COLUMNS\n" + "WHERE TABLE_ID = :id\n" + "AND NAME = :name;\n" + "END;\n", trx); + + if (error != DB_SUCCESS) { + return(error); + } + + dict_v_col_t* v_col = dict_table_get_nth_v_col_mysql( + table, drop_col->ind); + + /* Adjust column positions for all subsequent columns */ + for (ulint i = v_col->v_pos + 1; i < table->n_v_cols; i++) { + dict_v_col_t* t_col = dict_table_get_nth_v_col(table, i); + ulint old_p = dict_create_v_col_pos( + t_col->v_pos - n_prev_dropped, + t_col->m_col.ind - n_prev_dropped); + ulint new_p = dict_create_v_col_pos( + t_col->v_pos - 1 - n_prev_dropped, + ulint(t_col->m_col.ind) - 1 - n_prev_dropped); + + error = innobase_update_v_pos_sys_columns( + table, old_p, new_p, trx); + if (error != DB_SUCCESS) { + return(error); + } + error = innobase_update_v_pos_sys_virtual( + table, old_p, new_p, trx); + if (error != DB_SUCCESS) { + return(error); + } + } + + return(error); +} + +/** Delete virtual column's info from INNODB SYS_VIRTUAL +@param[in] table InnoDB table +@param[in] pos position of the virtual column to be deleted +@param[in] trx transaction +@return DB_SUCCESS if successful, otherwise error code */ +static +dberr_t +innobase_drop_one_virtual_sys_virtual( + const dict_table_t* table, + ulint pos, + trx_t* trx) +{ + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "id", table->id); + + pars_info_add_int4_literal(info, "pos", pos); + + dberr_t error = que_eval_sql( + info, + "PROCEDURE P () IS\n" + "BEGIN\n" + "DELETE FROM SYS_VIRTUAL\n" + "WHERE TABLE_ID = :id\n" + "AND POS = :pos;\n" + "END;\n", trx); + + return(error); +} + +/** Update system table for dropping virtual column(s) +@param[in] ha_alter_info Data used during in-place alter +@param[in] user_table InnoDB table +@param[in] trx transaction +@retval true Failure +@retval false Success */ +static +bool +innobase_drop_virtual_try( + const Alter_inplace_info* ha_alter_info, + const dict_table_t* user_table, + trx_t* trx) +{ + ha_innobase_inplace_ctx* ctx; + dberr_t err = DB_SUCCESS; + + ctx = static_cast<ha_innobase_inplace_ctx*> + (ha_alter_info->handler_ctx); + + for (unsigned i = 0; i < ctx->num_to_drop_vcol; i++) { + + ulint pos = dict_create_v_col_pos( + ctx->drop_vcol[i].v_pos - i, + ctx->drop_vcol[i].m_col.ind - i); + err = innobase_drop_one_virtual_sys_virtual( + user_table, pos, trx); + + if (err != DB_SUCCESS) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: DROP COLUMN...VIRTUAL"); + return(true); + } + + err = innobase_drop_one_virtual_sys_columns( + user_table, ctx->drop_vcol_name[i], + &(ctx->drop_vcol[i].m_col), i, trx); + + if (err != DB_SUCCESS) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: DROP COLUMN...VIRTUAL"); + return(true); + } + } + + return false; +} + +/** Serialise metadata of dropped or reordered columns. +@param[in,out] heap memory heap for allocation +@param[out] field data field with the metadata */ +inline +void dict_table_t::serialise_columns(mem_heap_t* heap, dfield_t* field) const +{ + DBUG_ASSERT(instant); + const dict_index_t& index = *UT_LIST_GET_FIRST(indexes); + unsigned n_fixed = index.first_user_field(); + unsigned num_non_pk_fields = index.n_fields - n_fixed; + + ulint len = 4 + num_non_pk_fields * 2; + + byte* data = static_cast<byte*>(mem_heap_alloc(heap, len)); + + dfield_set_data(field, data, len); + + mach_write_to_4(data, num_non_pk_fields); + + data += 4; + + for (ulint i = n_fixed; i < index.n_fields; i++) { + mach_write_to_2(data, instant->field_map[i - n_fixed]); + data += 2; + } +} + +/** Construct the metadata record for instant ALTER TABLE. +@param[in] row dummy or default values for existing columns +@param[in,out] heap memory heap for allocations +@return metadata record */ +inline +dtuple_t* +dict_index_t::instant_metadata(const dtuple_t& row, mem_heap_t* heap) const +{ + ut_ad(is_primary()); + dtuple_t* entry; + + if (!table->instant) { + entry = row_build_index_entry(&row, NULL, this, heap); + entry->info_bits = REC_INFO_METADATA_ADD; + return entry; + } + + entry = dtuple_create(heap, n_fields + 1); + entry->n_fields_cmp = n_uniq; + entry->info_bits = REC_INFO_METADATA_ALTER; + + const dict_field_t* field = fields; + + for (uint i = 0; i <= n_fields; i++, field++) { + dfield_t* dfield = dtuple_get_nth_field(entry, i); + + if (i == first_user_field()) { + table->serialise_columns(heap, dfield); + dfield->type.metadata_blob_init(); + field--; + continue; + } + + ut_ad(!field->col->is_virtual()); + + if (field->col->is_dropped()) { + dict_col_copy_type(field->col, &dfield->type); + if (field->col->is_nullable()) { + dfield_set_null(dfield); + } else { + dfield_set_data(dfield, field_ref_zero, + field->fixed_len); + } + continue; + } + + const dfield_t* s = dtuple_get_nth_field(&row, field->col->ind); + ut_ad(dict_col_type_assert_equal(field->col, &s->type)); + *dfield = *s; + + if (dfield_is_null(dfield)) { + continue; + } + + if (dfield_is_ext(dfield)) { + ut_ad(i > first_user_field()); + ut_ad(!field->prefix_len); + ut_ad(dfield->len >= FIELD_REF_SIZE); + dfield_set_len(dfield, dfield->len - FIELD_REF_SIZE); + } + + if (!field->prefix_len) { + continue; + } + + ut_ad(field->col->ord_part); + ut_ad(i < n_uniq); + + ulint len = dtype_get_at_most_n_mbchars( + field->col->prtype, + field->col->mbminlen, field->col->mbmaxlen, + field->prefix_len, dfield->len, + static_cast<char*>(dfield_get_data(dfield))); + dfield_set_len(dfield, len); + } + + return entry; +} + +/** Insert or update SYS_COLUMNS and the hidden metadata record +for instant ALTER TABLE. +@param[in] ha_alter_info ALTER TABLE context +@param[in,out] ctx ALTER TABLE context for the current partition +@param[in] altered_table MySQL table that is being altered +@param[in] table MySQL table as it is before the ALTER operation +@param[in,out] trx dictionary transaction +@retval true failure +@retval false success */ +static bool innobase_instant_try( + const Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx* ctx, + const TABLE* altered_table, + const TABLE* table, + trx_t* trx) +{ + DBUG_ASSERT(!ctx->need_rebuild()); + DBUG_ASSERT(ctx->is_instant()); + + dict_table_t* user_table = ctx->old_table; + + dict_index_t* index = dict_table_get_first_index(user_table); + const unsigned n_old_fields = index->n_fields; + const dict_col_t* old_cols = user_table->cols; + DBUG_ASSERT(user_table->n_cols == ctx->old_n_cols); + +#ifdef BTR_CUR_HASH_ADAPT + /* Acquire the ahi latch to avoid a race condition + between ahi access and instant alter table */ + srw_spin_lock* ahi_latch = btr_search_sys.get_latch(*index); + ahi_latch->wr_lock(SRW_LOCK_CALL); +#endif /* BTR_CUR_HASH_ADAPT */ + const bool metadata_changed = ctx->instant_column(); +#ifdef BTR_CUR_HASH_ADAPT + ahi_latch->wr_unlock(); +#endif /* BTR_CUR_HASH_ADAPT */ + + DBUG_ASSERT(index->n_fields >= n_old_fields); + /* The table may have been emptied and may have lost its + 'instantness' during this ALTER TABLE. */ + + /* Construct a table row of default values for the stored columns. */ + dtuple_t* row = dtuple_create(ctx->heap, user_table->n_cols); + dict_table_copy_types(row, user_table); + Field** af = altered_table->field; + Field** const end = altered_table->field + altered_table->s->fields; + ut_d(List_iterator_fast<Create_field> cf_it( + ha_alter_info->alter_info->create_list)); + if (ctx->first_alter_pos + && innobase_instant_drop_cols(user_table->id, + ctx->first_alter_pos - 1, trx)) { + return true; + } + for (uint i = 0; af < end; af++) { + if (!(*af)->stored_in_db()) { + ut_d(cf_it++); + continue; + } + + const dict_col_t* old = dict_table_t::find(old_cols, + ctx->col_map, + ctx->old_n_cols, i); + DBUG_ASSERT(!old || i >= ctx->old_n_cols - DATA_N_SYS_COLS + || old->ind == i + || (ctx->first_alter_pos + && old->ind >= ctx->first_alter_pos - 1)); + + dfield_t* d = dtuple_get_nth_field(row, i); + const dict_col_t* col = dict_table_get_nth_col(user_table, i); + DBUG_ASSERT(!col->is_virtual()); + DBUG_ASSERT(!col->is_dropped()); + DBUG_ASSERT(col->mtype != DATA_SYS); + DBUG_ASSERT(!strcmp((*af)->field_name.str, + dict_table_get_col_name(user_table, i))); + DBUG_ASSERT(old || col->is_added()); + + ut_d(const Create_field* new_field = cf_it++); + /* new_field->field would point to an existing column. + If it is NULL, the column was added by this ALTER TABLE. */ + ut_ad(!new_field->field == !old); + + if (col->is_added()) { + dfield_set_data(d, col->def_val.data, + col->def_val.len); + } else if ((*af)->real_maybe_null()) { + /* Store NULL for nullable 'core' columns. */ + dfield_set_null(d); + } else { + switch ((*af)->type()) { + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + variable_length: + /* Store the empty string for 'core' + variable-length NOT NULL columns. */ + dfield_set_data(d, field_ref_zero, 0); + break; + case MYSQL_TYPE_STRING: + if (col->mbminlen != col->mbmaxlen + && user_table->not_redundant()) { + goto variable_length; + } + /* fall through */ + default: + /* For fixed-length NOT NULL 'core' columns, + get a dummy default value from SQL. Note that + we will preserve the old values of these + columns when updating the metadata + record, to avoid unnecessary updates. */ + ulint len = (*af)->pack_length(); + DBUG_ASSERT(d->type.mtype != DATA_INT + || len <= 8); + row_mysql_store_col_in_innobase_format( + d, d->type.mtype == DATA_INT + ? static_cast<byte*>( + mem_heap_alloc(ctx->heap, len)) + : NULL, true, (*af)->ptr, len, + dict_table_is_comp(user_table)); + ut_ad(new_field->field->pack_length() == len); + } + } + + bool update = old && (!ctx->first_alter_pos + || i < ctx->first_alter_pos - 1); + DBUG_ASSERT(!old || col->same_format(*old)); + if (update + && old->prtype == d->type.prtype) { + /* The record is already present in SYS_COLUMNS. */ + } else if (innodb_insert_sys_columns(user_table->id, i, + (*af)->field_name.str, + d->type.mtype, + d->type.prtype, + d->type.len, 0, trx, + update)) { + return true; + } + + i++; + } + + if (innodb_update_cols(user_table, dict_table_encode_n_col( + unsigned(user_table->n_cols) + - DATA_N_SYS_COLS, + user_table->n_v_cols) + | (user_table->flags & DICT_TF_COMPACT) << 31, + trx)) { + return true; + } + + if (ctx->first_alter_pos) { +add_all_virtual: + for (uint i = 0; i < user_table->n_v_cols; i++) { + if (innobase_add_one_virtual( + user_table, + dict_table_get_v_col_name(user_table, i), + &user_table->v_cols[i], trx)) { + return true; + } + } + } else if (ha_alter_info->handler_flags & ALTER_DROP_VIRTUAL_COLUMN) { + if (innobase_instant_drop_cols(user_table->id, 65536, trx)) { + return true; + } + goto add_all_virtual; + } else if ((ha_alter_info->handler_flags & ALTER_ADD_VIRTUAL_COLUMN) + && innobase_add_virtual_try(ha_alter_info, user_table, + trx)) { + return true; + } + + if (!user_table->space) { + /* In case of ALTER TABLE...DISCARD TABLESPACE, + update only the metadata and transform the dictionary + cache entry to the canonical format. */ + index->clear_instant_alter(); + return false; + } + + unsigned i = unsigned(user_table->n_cols) - DATA_N_SYS_COLS; + DBUG_ASSERT(i >= altered_table->s->stored_fields); + DBUG_ASSERT(i <= altered_table->s->stored_fields + 1); + if (i > altered_table->s->fields) { + const dict_col_t& fts_doc_id = user_table->cols[i - 1]; + DBUG_ASSERT(!strcmp(fts_doc_id.name(*user_table), + FTS_DOC_ID_COL_NAME)); + DBUG_ASSERT(!fts_doc_id.is_nullable()); + DBUG_ASSERT(fts_doc_id.len == 8); + dfield_set_data(dtuple_get_nth_field(row, i - 1), + field_ref_zero, fts_doc_id.len); + } + byte trx_id[DATA_TRX_ID_LEN], roll_ptr[DATA_ROLL_PTR_LEN]; + dfield_set_data(dtuple_get_nth_field(row, i++), field_ref_zero, + DATA_ROW_ID_LEN); + dfield_set_data(dtuple_get_nth_field(row, i++), trx_id, sizeof trx_id); + dfield_set_data(dtuple_get_nth_field(row, i),roll_ptr,sizeof roll_ptr); + DBUG_ASSERT(i + 1 == user_table->n_cols); + + trx_write_trx_id(trx_id, trx->id); + /* The DB_ROLL_PTR will be assigned later, when allocating undo log. + Silence a Valgrind warning in dtuple_validate() when + row_ins_clust_index_entry_low() searches for the insert position. */ + memset(roll_ptr, 0, sizeof roll_ptr); + + dtuple_t* entry = index->instant_metadata(*row, ctx->heap); + mtr_t mtr; + mtr.start(); + index->set_modified(mtr); + btr_pcur_t pcur; + dberr_t err= pcur.open_leaf(true, index, BTR_MODIFY_TREE, &mtr); + if (err != DB_SUCCESS) { +func_exit: + mtr.commit(); + + if (err != DB_SUCCESS) { + my_error_innodb(err, table->s->table_name.str, + user_table->flags); + return true; + } + return false; + } + ut_ad(btr_pcur_is_before_first_on_page(&pcur)); + + buf_block_t* block = btr_pcur_get_block(&pcur); + ut_ad(page_is_leaf(block->page.frame)); + ut_ad(!page_has_prev(block->page.frame)); + ut_ad(!buf_block_get_page_zip(block)); + const rec_t* rec = btr_pcur_move_to_next_on_page(&pcur); + if (UNIV_UNLIKELY(!rec)) { + err = DB_CORRUPTION; + goto func_exit; + } + + que_thr_t* thr = pars_complete_graph_for_exec( + NULL, trx, ctx->heap, NULL); + page_id_t id{block->page.id()}; + const bool is_root = id.page_no() == index->page; + + if (rec_is_metadata(rec, *index)) { + ut_ad(page_rec_is_user_rec(rec)); + if (is_root + && !rec_is_alter_metadata(rec, *index) + && !index->table->instant + && !page_has_next(block->page.frame) + && page_rec_is_last(rec, block->page.frame)) { + goto empty_table; + } + + if (!metadata_changed) { + goto func_exit; + } + + /* Ensure that the root page is in the correct format. */ + id.set_page_no(index->page); + buf_block_t* root = mtr.get_already_latched( + id, MTR_MEMO_PAGE_SX_FIX); + + if (UNIV_UNLIKELY(!root)) { + err = DB_CORRUPTION; + goto func_exit; + } + + if (fil_page_get_type(root->page.frame) + != FIL_PAGE_TYPE_INSTANT) { + DBUG_ASSERT("wrong page type" == 0); + err = DB_CORRUPTION; + goto func_exit; + } + + btr_set_instant(root, *index, &mtr); + + /* Extend the record with any added columns. */ + uint n = uint(index->n_fields) - n_old_fields; + /* Reserve room for DB_TRX_ID,DB_ROLL_PTR and any + non-updated off-page columns in case they are moved off + page as a result of the update. */ + const uint16_t f = user_table->instant != NULL; + upd_t* update = upd_create(index->n_fields + f, ctx->heap); + update->n_fields = n + f; + update->info_bits = f + ? REC_INFO_METADATA_ALTER + : REC_INFO_METADATA_ADD; + if (f) { + upd_field_t* uf = upd_get_nth_field(update, 0); + uf->field_no = index->first_user_field(); + uf->new_val = entry->fields[uf->field_no]; + DBUG_ASSERT(!dfield_is_ext(&uf->new_val)); + DBUG_ASSERT(!dfield_is_null(&uf->new_val)); + } + + /* Add the default values for instantly added columns */ + unsigned j = f; + + for (unsigned k = n_old_fields; k < index->n_fields; k++) { + upd_field_t* uf = upd_get_nth_field(update, j++); + uf->field_no = static_cast<uint16_t>(k + f); + uf->new_val = entry->fields[k + f]; + + ut_ad(j <= n + f); + } + + ut_ad(j == n + f); + + rec_offs* offsets = NULL; + mem_heap_t* offsets_heap = NULL; + big_rec_t* big_rec; + err = btr_cur_pessimistic_update( + BTR_NO_LOCKING_FLAG | BTR_KEEP_POS_FLAG, + btr_pcur_get_btr_cur(&pcur), + &offsets, &offsets_heap, ctx->heap, + &big_rec, update, UPD_NODE_NO_ORD_CHANGE, + thr, trx->id, &mtr); + if (err == DB_SUCCESS) { + offsets = rec_get_offsets( + btr_pcur_get_rec(&pcur), index, offsets, + index->n_core_fields, ULINT_UNDEFINED, + &offsets_heap); + } + + if (big_rec) { + if (err == DB_SUCCESS) { + err = btr_store_big_rec_extern_fields( + &pcur, offsets, big_rec, &mtr, + BTR_STORE_UPDATE); + } + + dtuple_big_rec_free(big_rec); + } + if (offsets_heap) { + mem_heap_free(offsets_heap); + } + ut_free(pcur.old_rec_buf); + goto func_exit; + } else if (is_root && page_rec_is_supremum(rec) + && !index->table->instant) { +empty_table: + /* The table is empty. */ + ut_ad(fil_page_index_page_check(block->page.frame)); + ut_ad(!page_has_siblings(block->page.frame)); + ut_ad(block->page.id().page_no() == index->page); + /* MDEV-17383: free metadata BLOBs! */ + btr_page_empty(block, NULL, index, 0, &mtr); + if (index->is_instant()) { + index->clear_instant_add(); + } + goto func_exit; + } else if (!user_table->is_instant()) { + ut_ad(!user_table->not_redundant()); + goto func_exit; + } + + /* Convert the table to the instant ALTER TABLE format. */ + mtr.commit(); + mtr.start(); + index->set_modified(mtr); + if (buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, &mtr, + &err)) { + if (fil_page_get_type(root->page.frame) != FIL_PAGE_INDEX) { + DBUG_ASSERT("wrong page type" == 0); + err = DB_CORRUPTION; + goto func_exit; + } + + btr_set_instant(root, *index, &mtr); + mtr.commit(); + mtr.start(); + index->set_modified(mtr); + err = row_ins_clust_index_entry_low( + BTR_NO_LOCKING_FLAG, BTR_MODIFY_TREE, index, + index->n_uniq, entry, 0, thr); + } + + goto func_exit; +} + +/** Adjust the create index column number from "New table" to +"old InnoDB table" while we are doing dropping virtual column. Since we do +not create separate new table for the dropping/adding virtual columns. +To correctly find the indexed column, we will need to find its col_no +in the "Old Table", not the "New table". +@param[in] ha_alter_info Data used during in-place alter +@param[in] old_table MySQL table as it is before the ALTER operation +@param[in] num_v_dropped number of virtual column dropped +@param[in,out] index_def index definition */ +static +void +innodb_v_adjust_idx_col( + const Alter_inplace_info* ha_alter_info, + const TABLE* old_table, + ulint num_v_dropped, + index_def_t* index_def) +{ + for (ulint i = 0; i < index_def->n_fields; i++) { +#ifdef UNIV_DEBUG + bool col_found = false; +#endif /* UNIV_DEBUG */ + ulint num_v = 0; + + index_field_t* index_field = &index_def->fields[i]; + + /* Only adjust virtual column col_no, since non-virtual + column position (in non-vcol list) won't change unless + table rebuild */ + if (!index_field->is_v_col) { + continue; + } + + const Field* field = NULL; + + /* Found the field in the new table */ + for (const Create_field& new_field : + ha_alter_info->alter_info->create_list) { + if (new_field.stored_in_db()) { + continue; + } + + field = new_field.field; + + if (num_v == index_field->col_no) { + break; + } + num_v++; + } + + if (!field) { + /* this means the field is a newly added field, this + should have been blocked when we drop virtual column + at the same time */ + ut_ad(num_v_dropped > 0); + ut_a(0); + } + + ut_ad(!field->stored_in_db()); + + num_v = 0; + + /* Look for its position in old table */ + for (uint old_i = 0; old_table->field[old_i]; old_i++) { + if (old_table->field[old_i] == field) { + /* Found it, adjust its col_no to its position + in old table */ + index_def->fields[i].col_no = num_v; + ut_d(col_found = true); + break; + } + + num_v += !old_table->field[old_i]->stored_in_db(); + } + + ut_ad(col_found); + } +} + +/** Create index metadata in the data dictionary. +@param[in,out] trx dictionary transaction +@param[in,out] index index being created +@param[in] mode encryption mode (for creating a table) +@param[in] key_id encryption key identifier (for creating a table) +@param[in] add_v virtual columns that are being added, or NULL +@return the created index */ +MY_ATTRIBUTE((nonnull(1,2), warn_unused_result)) +static +dict_index_t* +create_index_dict( + trx_t* trx, + dict_index_t* index, + fil_encryption_t mode, + uint32_t key_id, + const dict_add_v_col_t* add_v) +{ + DBUG_ENTER("create_index_dict"); + + mem_heap_t* heap = mem_heap_create(512); + ind_node_t* node = ind_create_graph_create( + index, index->table->name.m_name, heap, mode, key_id, add_v); + que_thr_t* thr = pars_complete_graph_for_exec(node, trx, heap, NULL); + + que_fork_start_command( + static_cast<que_fork_t*>(que_node_get_parent(thr))); + + que_run_threads(thr); + + DBUG_ASSERT(trx->error_state != DB_SUCCESS || index != node->index); + DBUG_ASSERT(trx->error_state != DB_SUCCESS || node->index); + index = node->index; + + que_graph_free((que_t*) que_node_get_parent(thr)); + + DBUG_RETURN(index); +} + +/** Update internal structures with concurrent writes blocked, +while preparing ALTER TABLE. + +@param ha_alter_info Data used during in-place alter +@param altered_table MySQL table that is being altered +@param old_table MySQL table as it is before the ALTER operation +@param table_name Table name in MySQL +@param flags Table and tablespace flags +@param flags2 Additional table flags +@param fts_doc_id_col The column number of FTS_DOC_ID +@param add_fts_doc_id Flag: add column FTS_DOC_ID? +@param add_fts_doc_id_idx Flag: add index FTS_DOC_ID_INDEX (FTS_DOC_ID)? + +@retval true Failure +@retval false Success +*/ +static MY_ATTRIBUTE((warn_unused_result, nonnull(1,2,3,4))) +bool +prepare_inplace_alter_table_dict( +/*=============================*/ + Alter_inplace_info* ha_alter_info, + const TABLE* altered_table, + const TABLE* old_table, + const char* table_name, + ulint flags, + ulint flags2, + ulint fts_doc_id_col, + bool add_fts_doc_id, + bool add_fts_doc_id_idx) +{ + bool dict_locked = false; + ulint* add_key_nums; /* MySQL key numbers */ + index_def_t* index_defs; /* index definitions */ + dict_table_t* user_table; + dict_index_t* fts_index = NULL; + bool new_clustered = false; + dberr_t error = DB_SUCCESS; + ulint num_fts_index; + dict_add_v_col_t* add_v = NULL; + ha_innobase_inplace_ctx*ctx; + + DBUG_ENTER("prepare_inplace_alter_table_dict"); + + ctx = static_cast<ha_innobase_inplace_ctx*> + (ha_alter_info->handler_ctx); + + DBUG_ASSERT((ctx->add_autoinc != ULINT_UNDEFINED) + == (ctx->sequence.max_value() > 0)); + DBUG_ASSERT(!ctx->num_to_drop_index == !ctx->drop_index); + DBUG_ASSERT(!ctx->num_to_drop_fk == !ctx->drop_fk); + DBUG_ASSERT(!add_fts_doc_id || add_fts_doc_id_idx); + DBUG_ASSERT(!add_fts_doc_id_idx + || innobase_fulltext_exist(altered_table)); + DBUG_ASSERT(!ctx->defaults); + DBUG_ASSERT(!ctx->add_index); + DBUG_ASSERT(!ctx->add_key_numbers); + DBUG_ASSERT(!ctx->num_to_add_index); + + user_table = ctx->new_table; + + switch (ha_alter_info->inplace_supported) { + default: break; + case HA_ALTER_INPLACE_INSTANT: + case HA_ALTER_INPLACE_NOCOPY_LOCK: + case HA_ALTER_INPLACE_NOCOPY_NO_LOCK: + /* If we promised ALGORITHM=NOCOPY or ALGORITHM=INSTANT, + we must retain the original ROW_FORMAT of the table. */ + flags = (user_table->flags & (DICT_TF_MASK_COMPACT + | DICT_TF_MASK_ZIP_SSIZE + | DICT_TF_MASK_ATOMIC_BLOBS)) + | (flags & ~(DICT_TF_MASK_COMPACT + | DICT_TF_MASK_ZIP_SSIZE + | DICT_TF_MASK_ATOMIC_BLOBS)); + } + + trx_start_if_not_started_xa(ctx->prebuilt->trx, true); + + if (ha_alter_info->handler_flags + & ALTER_DROP_VIRTUAL_COLUMN) { + if (prepare_inplace_drop_virtual(ha_alter_info, old_table)) { + DBUG_RETURN(true); + } + } + + if (ha_alter_info->handler_flags + & ALTER_ADD_VIRTUAL_COLUMN) { + if (prepare_inplace_add_virtual( + ha_alter_info, altered_table, old_table)) { + DBUG_RETURN(true); + } + + /* Need information for newly added virtual columns + for create index */ + + if (ha_alter_info->handler_flags + & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) { + for (ulint i = 0; i < ctx->num_to_add_vcol; i++) { + /* Set mbminmax for newly added column */ + dict_col_t& col = ctx->add_vcol[i].m_col; + unsigned mbminlen, mbmaxlen; + dtype_get_mblen(col.mtype, col.prtype, + &mbminlen, &mbmaxlen); + col.mbminlen = mbminlen & 7; + col.mbmaxlen = mbmaxlen & 7; + } + add_v = static_cast<dict_add_v_col_t*>( + mem_heap_alloc(ctx->heap, sizeof *add_v)); + add_v->n_v_col = ctx->num_to_add_vcol; + add_v->v_col = ctx->add_vcol; + add_v->v_col_name = ctx->add_vcol_name; + } + } + + /* There should be no order change for virtual columns coming in + here */ + ut_ad(check_v_col_in_order(old_table, altered_table, ha_alter_info)); + + /* Create table containing all indexes to be built in this + ALTER TABLE ADD INDEX so that they are in the correct order + in the table. */ + + ctx->num_to_add_index = ha_alter_info->index_add_count; + + ut_ad(ctx->prebuilt->trx->mysql_thd != NULL); + const char* path = thd_innodb_tmpdir( + ctx->prebuilt->trx->mysql_thd); + + index_defs = ctx->create_key_defs( + ha_alter_info, altered_table, + num_fts_index, + fts_doc_id_col, add_fts_doc_id, add_fts_doc_id_idx, + old_table); + + new_clustered = (DICT_CLUSTERED & index_defs[0].ind_type) != 0; + + create_table_info_t info(ctx->prebuilt->trx->mysql_thd, altered_table, + ha_alter_info->create_info, NULL, NULL, + srv_file_per_table); + + /* The primary index would be rebuilt if a FTS Doc ID + column is to be added, and the primary index definition + is just copied from old table and stored in indexdefs[0] */ + DBUG_ASSERT(!add_fts_doc_id || new_clustered); + DBUG_ASSERT(!!new_clustered == + (innobase_need_rebuild(ha_alter_info, old_table) + || add_fts_doc_id)); + + /* Allocate memory for dictionary index definitions */ + + ctx->add_index = static_cast<dict_index_t**>( + mem_heap_zalloc(ctx->heap, ctx->num_to_add_index + * sizeof *ctx->add_index)); + ctx->add_key_numbers = add_key_nums = static_cast<ulint*>( + mem_heap_alloc(ctx->heap, ctx->num_to_add_index + * sizeof *ctx->add_key_numbers)); + + const bool fts_exist = ctx->new_table->flags2 + & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS); + /* Acquire a lock on the table before creating any indexes. */ + bool table_lock_failed = false; + + if (!ctx->online) { +acquire_lock: + ctx->prebuilt->trx->op_info = "acquiring table lock"; + error = lock_table_for_trx(user_table, ctx->trx, LOCK_S); + } else if (add_key_nums) { + /* FIXME: trx_resurrect_table_locks() will not resurrect + MDL for any recovered transactions that may hold locks on + the table. We will prevent race conditions by "unnecessarily" + acquiring an InnoDB table lock even for online operation, + to ensure that the rollback of recovered transactions will + not run concurrently with online ADD INDEX. */ + user_table->lock_mutex_lock(); + for (lock_t *lock = UT_LIST_GET_FIRST(user_table->locks); + lock; + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock)) { + if (lock->trx->is_recovered) { + user_table->lock_mutex_unlock(); + goto acquire_lock; + } + } + user_table->lock_mutex_unlock(); + } + + if (fts_exist) { + purge_sys.stop_FTS(*ctx->new_table); + if (error == DB_SUCCESS) { + error = fts_lock_tables(ctx->trx, *ctx->new_table); + } + } + + if (error == DB_SUCCESS) { + error = lock_sys_tables(ctx->trx); + } + + if (error != DB_SUCCESS) { + table_lock_failed = true; + goto error_handling; + } + + /* Latch the InnoDB data dictionary exclusively so that no deadlocks + or lock waits can happen in it during an index create operation. */ + + row_mysql_lock_data_dictionary(ctx->trx); + dict_locked = true; + online_retry_drop_indexes_low(ctx->new_table, ctx->trx); + + ut_d(dict_table_check_for_dup_indexes( + ctx->new_table, CHECK_ABORTED_OK)); + + DBUG_EXECUTE_IF("innodb_OOM_prepare_inplace_alter", + error = DB_OUT_OF_MEMORY; + goto error_handling;); + + /* If a new clustered index is defined for the table we need + to rebuild the table with a temporary name. */ + + if (new_clustered) { + if (innobase_check_foreigns( + ha_alter_info, old_table, + user_table, ctx->drop_fk, ctx->num_to_drop_fk)) { +new_clustered_failed: + DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx); + ctx->trx->rollback(); + + ut_ad(user_table->get_ref_count() == 1); + + if (user_table->drop_aborted) { + row_mysql_unlock_data_dictionary(ctx->trx); + trx_start_for_ddl(ctx->trx); + if (lock_sys_tables(ctx->trx) == DB_SUCCESS) { + row_mysql_lock_data_dictionary( + ctx->trx); + online_retry_drop_indexes_low( + user_table, ctx->trx); + commit_unlock_and_unlink(ctx->trx); + } else { + ctx->trx->commit(); + } + row_mysql_lock_data_dictionary(ctx->trx); + } + + if (ctx->need_rebuild()) { + if (ctx->new_table) { + ut_ad(!ctx->new_table->cached); + dict_mem_table_free(ctx->new_table); + } + ctx->new_table = ctx->old_table; + } + + while (ctx->num_to_add_index--) { + if (dict_index_t*& i = ctx->add_index[ + ctx->num_to_add_index]) { + dict_mem_index_free(i); + i = NULL; + } + } + + goto err_exit; + } + + size_t prefixlen= strlen(mysql_data_home); + if (mysql_data_home[prefixlen-1] != FN_LIBCHAR) + prefixlen++; + size_t tablen = altered_table->s->path.length - prefixlen; + const char* part = ctx->old_table->name.part(); + size_t partlen = part ? strlen(part) : 0; + char* new_table_name = static_cast<char*>( + mem_heap_alloc(ctx->heap, tablen + partlen + 1)); + memcpy(new_table_name, + altered_table->s->path.str + prefixlen, tablen); +#ifdef _WIN32 + { + char *sep= strchr(new_table_name, FN_LIBCHAR); + sep[0]= '/'; + } +#endif + memcpy(new_table_name + tablen, part ? part : "", partlen + 1); + ulint n_cols = 0; + ulint n_v_cols = 0; + dtuple_t* defaults; + ulint z = 0; + + for (uint i = 0; i < altered_table->s->fields; i++) { + const Field* field = altered_table->field[i]; + + if (!field->stored_in_db()) { + n_v_cols++; + } else { + n_cols++; + } + } + + ut_ad(n_cols + n_v_cols == altered_table->s->fields); + + if (add_fts_doc_id) { + n_cols++; + DBUG_ASSERT(flags2 & DICT_TF2_FTS); + DBUG_ASSERT(add_fts_doc_id_idx); + flags2 |= DICT_TF2_FTS_ADD_DOC_ID + | DICT_TF2_FTS_HAS_DOC_ID + | DICT_TF2_FTS; + } + + DBUG_ASSERT(!add_fts_doc_id_idx || (flags2 & DICT_TF2_FTS)); + + ctx->new_table = dict_table_t::create( + {new_table_name, tablen + partlen}, nullptr, + n_cols + n_v_cols, n_v_cols, flags, flags2); + + /* The rebuilt indexed_table will use the renamed + column names. */ + ctx->col_names = NULL; + + if (DICT_TF_HAS_DATA_DIR(flags)) { + ctx->new_table->data_dir_path = + mem_heap_strdup(ctx->new_table->heap, + user_table->data_dir_path); + } + + for (uint i = 0; i < altered_table->s->fields; i++) { + const Field* field = altered_table->field[i]; + unsigned is_unsigned; + auto col_type = get_innobase_type_from_mysql_type( + &is_unsigned, field); + unsigned field_type = field->type() | is_unsigned; + const bool is_virtual = !field->stored_in_db(); + + /* we assume in dtype_form_prtype() that this + fits in two bytes */ + ut_a(field_type <= MAX_CHAR_COLL_NUM); + + if (!field->real_maybe_null()) { + field_type |= DATA_NOT_NULL; + } + + if (field->binary()) { + field_type |= DATA_BINARY_TYPE; + } + + if (altered_table->versioned()) { + if (i == altered_table->s->vers.start_fieldno) { + field_type |= DATA_VERS_START; + } else if (i == + altered_table->s->vers.end_fieldno) { + field_type |= DATA_VERS_END; + } else if (!(field->flags + & VERS_UPDATE_UNVERSIONED_FLAG)) { + field_type |= DATA_VERSIONED; + } + } + + unsigned charset_no; + + if (dtype_is_string_type(col_type)) { + charset_no = field->charset()->number; + + if (charset_no > MAX_CHAR_COLL_NUM) { + my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB", + field->field_name.str); + goto new_clustered_failed; + } + } else { + charset_no = 0; + } + + auto col_len = field->pack_length(); + + /* The MySQL pack length contains 1 or 2 bytes + length field for a true VARCHAR. Let us + subtract that, so that the InnoDB column + length in the InnoDB data dictionary is the + real maximum byte length of the actual data. */ + + if (field->type() == MYSQL_TYPE_VARCHAR) { + uint32 length_bytes + = static_cast<const Field_varstring*>( + field)->length_bytes; + + col_len -= length_bytes; + + if (length_bytes == 2) { + field_type |= DATA_LONG_TRUE_VARCHAR; + } + + } + + if (dict_col_name_is_reserved(field->field_name.str)) { +wrong_column_name: + dict_mem_table_free(ctx->new_table); + ctx->new_table = ctx->old_table; + my_error(ER_WRONG_COLUMN_NAME, MYF(0), + field->field_name.str); + goto new_clustered_failed; + } + + /** Note the FTS_DOC_ID name is case sensitive due + to internal query parser. + FTS_DOC_ID column must be of BIGINT NOT NULL type + and it should be in all capitalized characters */ + if (!innobase_strcasecmp(field->field_name.str, + FTS_DOC_ID_COL_NAME)) { + if (col_type != DATA_INT + || field->real_maybe_null() + || col_len != sizeof(doc_id_t) + || strcmp(field->field_name.str, + FTS_DOC_ID_COL_NAME)) { + goto wrong_column_name; + } + } + + if (is_virtual) { + dict_mem_table_add_v_col( + ctx->new_table, ctx->heap, + field->field_name.str, + col_type, + dtype_form_prtype( + field_type, charset_no) + | DATA_VIRTUAL, + col_len, i, 0); + } else { + dict_mem_table_add_col( + ctx->new_table, ctx->heap, + field->field_name.str, + col_type, + dtype_form_prtype( + field_type, charset_no), + col_len); + } + } + + if (n_v_cols) { + for (uint i = 0; i < altered_table->s->fields; i++) { + dict_v_col_t* v_col; + const Field* field = altered_table->field[i]; + + if (!!field->stored_in_db()) { + continue; + } + v_col = dict_table_get_nth_v_col( + ctx->new_table, z); + z++; + innodb_base_col_setup( + ctx->new_table, field, v_col); + } + } + + if (add_fts_doc_id) { + fts_add_doc_id_column(ctx->new_table, ctx->heap); + ctx->new_table->fts->doc_col = fts_doc_id_col; + ut_ad(fts_doc_id_col + == altered_table->s->fields - n_v_cols); + } else if (ctx->new_table->fts) { + ctx->new_table->fts->doc_col = fts_doc_id_col; + } + + dict_table_add_system_columns(ctx->new_table, ctx->heap); + + if (ha_alter_info->handler_flags & INNOBASE_DEFAULTS) { + defaults = dtuple_create_with_vcol( + ctx->heap, + dict_table_get_n_cols(ctx->new_table), + dict_table_get_n_v_cols(ctx->new_table)); + + dict_table_copy_types(defaults, ctx->new_table); + } else { + defaults = NULL; + } + + ctx->col_map = innobase_build_col_map( + ha_alter_info, altered_table, old_table, + ctx->new_table, user_table, defaults, ctx->heap); + ctx->defaults = defaults; + } else { + DBUG_ASSERT(!innobase_need_rebuild(ha_alter_info, old_table)); + DBUG_ASSERT(old_table->s->primary_key + == altered_table->s->primary_key); + + for (dict_index_t* index + = dict_table_get_first_index(user_table); + index != NULL; + index = dict_table_get_next_index(index)) { + if (!index->to_be_dropped && index->is_corrupted()) { + my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0)); + goto error_handled; + } + } + + for (dict_index_t* index + = dict_table_get_first_index(user_table); + index != NULL; + index = dict_table_get_next_index(index)) { + if (!index->to_be_dropped && index->is_corrupted()) { + my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0)); + goto error_handled; + } + } + + if (!ctx->new_table->fts + && innobase_fulltext_exist(altered_table)) { + ctx->new_table->fts = fts_create( + ctx->new_table); + ctx->new_table->fts->doc_col = fts_doc_id_col; + } + + /* Check if we need to update mtypes of legacy GIS columns. + This check is only needed when we don't have to rebuild + the table, since rebuild would update all mtypes for GIS + columns */ + error = innobase_check_gis_columns( + ha_alter_info, ctx->new_table, ctx->trx); + if (error != DB_SUCCESS) { + ut_ad(error == DB_ERROR); + my_error(ER_TABLE_CANT_HANDLE_SPKEYS, MYF(0), "SYS_COLUMNS"); + goto error_handled; + } + } + + ut_ad(new_clustered == ctx->need_rebuild()); + + /* Create the index metadata. */ + for (ulint a = 0; a < ctx->num_to_add_index; a++) { + if (index_defs[a].ind_type & DICT_VIRTUAL + && ctx->num_to_drop_vcol > 0 && !new_clustered) { + innodb_v_adjust_idx_col(ha_alter_info, old_table, + ctx->num_to_drop_vcol, + &index_defs[a]); + } + + ctx->add_index[a] = row_merge_create_index( + ctx->new_table, &index_defs[a], add_v); + + add_key_nums[a] = index_defs[a].key_number; + + DBUG_ASSERT(ctx->add_index[a]->is_committed() + == !!new_clustered); + } + + DBUG_ASSERT(!ctx->need_rebuild() + || !ctx->new_table->persistent_autoinc); + + if (ctx->need_rebuild() && instant_alter_column_possible( + *user_table, ha_alter_info, old_table, altered_table, + ha_innobase::is_innodb_strict_mode(ctx->trx->mysql_thd))) { + for (uint a = 0; a < ctx->num_to_add_index; a++) { + ctx->add_index[a]->table = ctx->new_table; + error = dict_index_add_to_cache( + ctx->add_index[a], FIL_NULL, add_v); + ut_a(error == DB_SUCCESS); + } + + DBUG_ASSERT(ha_alter_info->key_count + /* hidden GEN_CLUST_INDEX in InnoDB */ + + dict_index_is_auto_gen_clust( + dict_table_get_first_index(ctx->new_table)) + /* hidden FTS_DOC_ID_INDEX in InnoDB */ + + (ctx->old_table->fts_doc_id_index + && innobase_fts_check_doc_id_index_in_def( + altered_table->s->keys, + altered_table->key_info) + != FTS_EXIST_DOC_ID_INDEX) + == ctx->num_to_add_index); + + ctx->num_to_add_index = 0; + ctx->add_index = NULL; + + uint i = 0; // index of stored columns ctx->new_table->cols[] + Field **af = altered_table->field; + + for (const Create_field& new_field : + ha_alter_info->alter_info->create_list) { + DBUG_ASSERT(!new_field.field + || std::find(old_table->field, + old_table->field + + old_table->s->fields, + new_field.field) != + old_table->field + old_table->s->fields); + DBUG_ASSERT(new_field.field + || !strcmp(new_field.field_name.str, + (*af)->field_name.str)); + + if (!(*af)->stored_in_db()) { + af++; + continue; + } + + dict_col_t* col = dict_table_get_nth_col( + ctx->new_table, i); + DBUG_ASSERT(!strcmp((*af)->field_name.str, + dict_table_get_col_name(ctx->new_table, + i))); + DBUG_ASSERT(!col->is_added()); + + if (new_field.field) { + /* This is a pre-existing column, + possibly at a different position. */ + } else if ((*af)->is_real_null()) { + /* DEFAULT NULL */ + col->def_val.len = UNIV_SQL_NULL; + } else { + switch ((*af)->type()) { + case MYSQL_TYPE_VARCHAR: + col->def_val.len = reinterpret_cast + <const Field_varstring*> + ((*af))->get_length(); + col->def_val.data = reinterpret_cast + <const Field_varstring*> + ((*af))->get_data(); + break; + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + col->def_val.len = reinterpret_cast + <const Field_blob*> + ((*af))->get_length(); + col->def_val.data = reinterpret_cast + <const Field_blob*> + ((*af))->get_ptr(); + break; + default: + dfield_t d; + dict_col_copy_type(col, &d.type); + ulint len = (*af)->pack_length(); + DBUG_ASSERT(len <= 8 + || d.type.mtype + != DATA_INT); + row_mysql_store_col_in_innobase_format( + &d, + d.type.mtype == DATA_INT + ? static_cast<byte*>( + mem_heap_alloc( + ctx->heap, + len)) + : NULL, + true, (*af)->ptr, len, + dict_table_is_comp( + user_table)); + col->def_val.len = d.len; + col->def_val.data = d.data; + } + } + + i++; + af++; + } + + DBUG_ASSERT(af == altered_table->field + + altered_table->s->fields); + /* There might exist a hidden FTS_DOC_ID column for + FULLTEXT INDEX. If it exists, the columns should have + been implicitly added by ADD FULLTEXT INDEX together + with instant ADD COLUMN. (If a hidden FTS_DOC_ID pre-existed, + then the ctx->col_map[] check should have prevented + adding visible user columns after that.) */ + DBUG_ASSERT(DATA_N_SYS_COLS + i == ctx->new_table->n_cols + || (1 + DATA_N_SYS_COLS + i + == ctx->new_table->n_cols + && !strcmp(dict_table_get_col_name( + ctx->new_table, i), + FTS_DOC_ID_COL_NAME))); + + if (altered_table->found_next_number_field) { + ctx->new_table->persistent_autoinc + = ctx->old_table->persistent_autoinc; + } + + ctx->prepare_instant(); + } + + if (ctx->need_rebuild()) { + DBUG_ASSERT(ctx->need_rebuild()); + DBUG_ASSERT(!ctx->is_instant()); + DBUG_ASSERT(num_fts_index <= 1); + DBUG_ASSERT(!ctx->online || num_fts_index == 0); + DBUG_ASSERT(!ctx->online + || !ha_alter_info->mdl_exclusive_after_prepare + || ctx->add_autoinc == ULINT_UNDEFINED); + DBUG_ASSERT(!ctx->online + || !innobase_need_rebuild(ha_alter_info, old_table) + || !innobase_fulltext_exist(altered_table)); + + uint32_t key_id = FIL_DEFAULT_ENCRYPTION_KEY; + fil_encryption_t mode = FIL_ENCRYPTION_DEFAULT; + + if (fil_space_t* s = user_table->space) { + if (const fil_space_crypt_t* c = s->crypt_data) { + key_id = c->key_id; + mode = c->encryption; + } + } + + if (ha_alter_info->handler_flags & ALTER_OPTIONS) { + const ha_table_option_struct& alt_opt= + *ha_alter_info->create_info->option_struct; + const ha_table_option_struct& opt= + *old_table->s->option_struct; + if (alt_opt.encryption != opt.encryption + || alt_opt.encryption_key_id + != opt.encryption_key_id) { + key_id = uint32_t(alt_opt.encryption_key_id); + mode = fil_encryption_t(alt_opt.encryption); + } + } + + if (dict_sys.find_table( + {ctx->new_table->name.m_name, + strlen(ctx->new_table->name.m_name)})) { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), + ctx->new_table->name.m_name); + goto new_clustered_failed; + } + + /* Create the table. */ + ctx->trx->dict_operation = true; + + error = row_create_table_for_mysql(ctx->new_table, ctx->trx); + + switch (error) { + case DB_SUCCESS: + DBUG_ASSERT(ctx->new_table->get_ref_count() == 0); + DBUG_ASSERT(ctx->new_table->id != 0); + break; + case DB_DUPLICATE_KEY: + my_error(HA_ERR_TABLE_EXIST, MYF(0), + altered_table->s->table_name.str); + goto new_table_failed; + case DB_UNSUPPORTED: + my_error(ER_UNSUPPORTED_EXTENSION, MYF(0), + altered_table->s->table_name.str); + goto new_table_failed; + default: + my_error_innodb(error, table_name, flags); +new_table_failed: + DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx); + ctx->new_table = NULL; + goto new_clustered_failed; + } + + for (ulint a = 0; a < ctx->num_to_add_index; a++) { + dict_index_t* index = ctx->add_index[a]; + const ulint n_v_col = index->get_new_n_vcol(); + index = create_index_dict(ctx->trx, index, + mode, key_id, add_v); + error = ctx->trx->error_state; + if (error != DB_SUCCESS) { + if (index) { + dict_mem_index_free(index); + } +error_handling_drop_uncached_1: + while (++a < ctx->num_to_add_index) { + dict_mem_index_free(ctx->add_index[a]); + } + goto error_handling; + } else { + DBUG_ASSERT(index != ctx->add_index[a]); + } + + ctx->add_index[a] = index; + /* For ALTER TABLE...FORCE or OPTIMIZE TABLE, + we may only issue warnings, because there will + be no schema change from the user perspective. */ + if (!info.row_size_is_acceptable( + *index, + !!(ha_alter_info->handler_flags + & ~(INNOBASE_INPLACE_IGNORE + | INNOBASE_ALTER_NOVALIDATE + | ALTER_RECREATE_TABLE)))) { + error = DB_TOO_BIG_RECORD; + goto error_handling_drop_uncached_1; + } + index->parser = index_defs[a].parser; + if (n_v_col) { + index->assign_new_v_col(n_v_col); + } + /* Note the id of the transaction that created this + index, we use it to restrict readers from accessing + this index, to ensure read consistency. */ + ut_ad(index->trx_id == ctx->trx->id); + + if (index->type & DICT_FTS) { + DBUG_ASSERT(num_fts_index == 1); + DBUG_ASSERT(!fts_index); + DBUG_ASSERT(index->type == DICT_FTS); + fts_index = ctx->add_index[a]; + } + } + + dict_index_t* clust_index = dict_table_get_first_index( + user_table); + dict_index_t* new_clust_index = dict_table_get_first_index( + ctx->new_table); + ut_ad(!new_clust_index->is_instant()); + /* row_merge_build_index() depends on the correct value */ + ut_ad(new_clust_index->n_core_null_bytes + == UT_BITS_IN_BYTES(new_clust_index->n_nullable)); + + if (const Field* ai = altered_table->found_next_number_field) { + const unsigned col_no = innodb_col_no(ai); + + ctx->new_table->persistent_autoinc = + (dict_table_get_nth_col_pos( + ctx->new_table, col_no, NULL) + 1) + & dict_index_t::MAX_N_FIELDS; + + /* Initialize the AUTO_INCREMENT sequence + to the rebuilt table from the old one. */ + if (!old_table->found_next_number_field + || !user_table->space) { + } else if (ib_uint64_t autoinc + = btr_read_autoinc(clust_index)) { + btr_write_autoinc(new_clust_index, autoinc); + } + } + + ctx->skip_pk_sort = innobase_pk_order_preserved( + ctx->col_map, clust_index, new_clust_index); + + DBUG_EXECUTE_IF("innodb_alter_table_pk_assert_no_sort", + DBUG_ASSERT(ctx->skip_pk_sort);); + + if (ctx->online) { + /* Allocate a log for online table rebuild. */ + clust_index->lock.x_lock(SRW_LOCK_CALL); + bool ok = row_log_allocate( + ctx->prebuilt->trx, + clust_index, ctx->new_table, + !(ha_alter_info->handler_flags + & ALTER_ADD_PK_INDEX), + ctx->defaults, ctx->col_map, path, + old_table, + ctx->allow_not_null); + clust_index->lock.x_unlock(); + + if (!ok) { + error = DB_OUT_OF_MEMORY; + goto error_handling; + } + } + } else if (ctx->num_to_add_index) { + ut_ad(!ctx->is_instant()); + + for (ulint a = 0; a < ctx->num_to_add_index; a++) { + dict_index_t* index = ctx->add_index[a]; + const ulint n_v_col = index->get_new_n_vcol(); + DBUG_EXECUTE_IF( + "create_index_metadata_fail", + if (a + 1 == ctx->num_to_add_index) { + ctx->trx->error_state = + DB_OUT_OF_FILE_SPACE; + goto index_created; + }); + index = create_index_dict(ctx->trx, index, + FIL_ENCRYPTION_DEFAULT, + FIL_DEFAULT_ENCRYPTION_KEY, + add_v); +#ifndef DBUG_OFF +index_created: +#endif + error = ctx->trx->error_state; + if (error != DB_SUCCESS) { + if (index) { + dict_mem_index_free(index); + } +error_handling_drop_uncached: + while (++a < ctx->num_to_add_index) { + dict_mem_index_free(ctx->add_index[a]); + } + goto error_handling; + } else { + DBUG_ASSERT(index != ctx->add_index[a]); + } + ctx->add_index[a]= index; + if (!info.row_size_is_acceptable(*index, true)) { + error = DB_TOO_BIG_RECORD; + goto error_handling_drop_uncached; + } + + index->parser = index_defs[a].parser; + if (n_v_col) { + index->assign_new_v_col(n_v_col); + } + + ctx->change_col_collation(index, *altered_table); + /* Note the id of the transaction that created this + index, we use it to restrict readers from accessing + this index, to ensure read consistency. */ + ut_ad(index->trx_id == ctx->trx->id); + + /* If ADD INDEX with LOCK=NONE has been + requested, allocate a modification log. */ + if (index->type & DICT_FTS) { + DBUG_ASSERT(num_fts_index == 1); + DBUG_ASSERT(!fts_index); + DBUG_ASSERT(index->type == DICT_FTS); + fts_index = ctx->add_index[a]; + /* Fulltext indexes are not covered + by a modification log. */ + } else if (!ctx->online + || !user_table->is_readable() + || !user_table->space) { + /* No need to allocate a modification log. */ + DBUG_ASSERT(!index->online_log); + } else { + index->lock.x_lock(SRW_LOCK_CALL); + + bool ok = row_log_allocate( + ctx->prebuilt->trx, + index, + NULL, true, NULL, NULL, + path, old_table, + ctx->allow_not_null); + + index->lock.x_unlock(); + + DBUG_EXECUTE_IF( + "innodb_OOM_prepare_add_index", + if (ok && a == 1) { + row_log_free( + index->online_log); + index->online_log = NULL; + ctx->old_table->indexes.start + ->online_log = nullptr; + ok = false; + }); + + if (!ok) { + error = DB_OUT_OF_MEMORY; + goto error_handling_drop_uncached; + } + } + } + } else if (ctx->is_instant() + && !info.row_size_is_acceptable(*user_table, true)) { + error = DB_TOO_BIG_RECORD; + goto error_handling; + } + + if (ctx->online && ctx->num_to_add_index) { + /* Assign a consistent read view for + row_merge_read_clustered_index(). */ + ctx->prebuilt->trx->read_view.open(ctx->prebuilt->trx); + } + + if (fts_index) { + ut_ad(ctx->trx->dict_operation); + ut_ad(ctx->trx->dict_operation_lock_mode); + ut_ad(dict_sys.locked()); + + DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS); + if (ctx->need_rebuild()) { + /* For !ctx->need_rebuild(), this will be set at + commit_cache_norebuild(). */ + ctx->new_table->fts_doc_id_index + = dict_table_get_index_on_name( + ctx->new_table, FTS_DOC_ID_INDEX_NAME); + DBUG_ASSERT(ctx->new_table->fts_doc_id_index != NULL); + } + + error = fts_create_index_tables(ctx->trx, fts_index, + ctx->new_table->id); + + DBUG_EXECUTE_IF("innodb_test_fail_after_fts_index_table", + error = DB_LOCK_WAIT_TIMEOUT; + goto error_handling;); + + if (error != DB_SUCCESS) { + goto error_handling; + } + + if (!ctx->new_table->fts + || ib_vector_size(ctx->new_table->fts->indexes) == 0) { + error = fts_create_common_tables( + ctx->trx, ctx->new_table, true); + + DBUG_EXECUTE_IF( + "innodb_test_fail_after_fts_common_table", + error = DB_LOCK_WAIT_TIMEOUT;); + + if (error != DB_SUCCESS) { + goto error_handling; + } + + error = innobase_fts_load_stopword( + ctx->new_table, ctx->trx, + ctx->prebuilt->trx->mysql_thd) + ? DB_SUCCESS : DB_ERROR; + + if (error != DB_SUCCESS) { + goto error_handling; + } + } + } + + DBUG_ASSERT(error == DB_SUCCESS); + + { + /* Commit the data dictionary transaction in order to release + the table locks on the system tables. This means that if + MariaDB is killed while rebuilding the table inside + row_merge_build_indexes(), ctx->new_table will not be dropped + by trx_rollback_active(). */ + ut_d(dict_table_check_for_dup_indexes(user_table, + CHECK_PARTIAL_OK)); + if (ctx->need_rebuild()) { + ctx->new_table->acquire(); + } + + /* fts_create_common_tables() may drop old common tables, + whose files would be deleted here. */ + commit_unlock_and_unlink(ctx->trx); + if (fts_exist) { + purge_sys.resume_FTS(); + } + + trx_start_for_ddl(ctx->trx); + ctx->prebuilt->trx_id = ctx->trx->id; + } + + if (ctx->old_table->fts) { + fts_sync_during_ddl(ctx->old_table); + } + + DBUG_RETURN(false); + +error_handling: + /* After an error, remove all those index definitions from the + dictionary which were defined. */ + + switch (error) { + case DB_TABLESPACE_EXISTS: + my_error(ER_TABLESPACE_EXISTS, MYF(0), "(unknown)"); + break; + case DB_DUPLICATE_KEY: + my_error(ER_DUP_KEY, MYF(0), "SYS_INDEXES"); + break; + default: + my_error_innodb(error, table_name, user_table->flags); + } + + ctx->trx->rollback(); + + ut_ad(!ctx->need_rebuild() + || !user_table->indexes.start->online_log); + + ctx->prebuilt->trx->error_info = NULL; + ctx->trx->error_state = DB_SUCCESS; + + if (false) { +error_handled: + ut_ad(!table_lock_failed); + ut_ad(ctx->trx->state == TRX_STATE_ACTIVE); + ut_ad(!ctx->trx->undo_no); + ut_ad(dict_locked); + } else if (table_lock_failed) { + if (!dict_locked) { + row_mysql_lock_data_dictionary(ctx->trx); + } + goto err_exit; + } else { + ut_ad(ctx->trx->state == TRX_STATE_NOT_STARTED); + if (new_clustered && !user_table->drop_aborted) { + goto err_exit; + } + if (dict_locked) { + row_mysql_unlock_data_dictionary(ctx->trx); + } + trx_start_for_ddl(ctx->trx); + dberr_t err= lock_sys_tables(ctx->trx); + row_mysql_lock_data_dictionary(ctx->trx); + if (err != DB_SUCCESS) { + goto err_exit; + } + } + + /* n_ref_count must be 1, because background threads cannot + be executing on this very table as we are + holding MDL_EXCLUSIVE. */ + ut_ad(ctx->online || user_table->get_ref_count() == 1); + + if (new_clustered) { + online_retry_drop_indexes_low(user_table, ctx->trx); + commit_unlock_and_unlink(ctx->trx); + row_mysql_lock_data_dictionary(ctx->trx); + } else { + row_merge_drop_indexes(ctx->trx, user_table, true); + ctx->trx->commit(); + } + + ut_d(dict_table_check_for_dup_indexes(user_table, CHECK_ALL_COMPLETE)); + ut_ad(!user_table->drop_aborted); + +err_exit: + /* Clear the to_be_dropped flag in the data dictionary cache. */ + for (ulint i = 0; i < ctx->num_to_drop_index; i++) { + DBUG_ASSERT(ctx->drop_index[i]->is_committed()); + DBUG_ASSERT(ctx->drop_index[i]->to_be_dropped); + ctx->drop_index[i]->to_be_dropped = 0; + } + + if (ctx->trx) { + row_mysql_unlock_data_dictionary(ctx->trx); + ctx->trx->rollback(); + ctx->trx->free(); + } + trx_commit_for_mysql(ctx->prebuilt->trx); + if (fts_exist) { + purge_sys.resume_FTS(); + } + + for (uint i = 0; i < ctx->num_to_add_fk; i++) { + if (ctx->add_fk[i]) { + dict_foreign_free(ctx->add_fk[i]); + } + } + + delete ctx; + ha_alter_info->handler_ctx = NULL; + + DBUG_RETURN(true); +} + +/* Check whether an index is needed for the foreign key constraint. +If so, if it is dropped, is there an equivalent index can play its role. +@return true if the index is needed and can't be dropped */ +static MY_ATTRIBUTE((nonnull(1,2,3,5), warn_unused_result)) +bool +innobase_check_foreign_key_index( +/*=============================*/ + Alter_inplace_info* ha_alter_info, /*!< in: Structure describing + changes to be done by ALTER + TABLE */ + dict_index_t* index, /*!< in: index to check */ + dict_table_t* indexed_table, /*!< in: table that owns the + foreign keys */ + const char** col_names, /*!< in: column names, or NULL + for indexed_table->col_names */ + trx_t* trx, /*!< in/out: transaction */ + dict_foreign_t** drop_fk, /*!< in: Foreign key constraints + to drop */ + ulint n_drop_fk) /*!< in: Number of foreign keys + to drop */ +{ + const dict_foreign_set* fks = &indexed_table->referenced_set; + + /* Check for all FK references from other tables to the index. */ + for (dict_foreign_set::const_iterator it = fks->begin(); + it != fks->end(); ++it) { + + dict_foreign_t* foreign = *it; + if (foreign->referenced_index != index) { + continue; + } + ut_ad(indexed_table == foreign->referenced_table); + + if (NULL == dict_foreign_find_index( + indexed_table, col_names, + foreign->referenced_col_names, + foreign->n_fields, index, + /*check_charsets=*/TRUE, + /*check_null=*/FALSE, + NULL, NULL, NULL) + && NULL == innobase_find_equiv_index( + foreign->referenced_col_names, + foreign->n_fields, + ha_alter_info->key_info_buffer, + span<uint>(ha_alter_info->index_add_buffer, + ha_alter_info->index_add_count))) { + + /* Index cannot be dropped. */ + trx->error_info = index; + return(true); + } + } + + fks = &indexed_table->foreign_set; + + /* Check for all FK references in current table using the index. */ + for (dict_foreign_set::const_iterator it = fks->begin(); + it != fks->end(); ++it) { + + dict_foreign_t* foreign = *it; + if (foreign->foreign_index != index) { + continue; + } + + ut_ad(indexed_table == foreign->foreign_table); + + if (!innobase_dropping_foreign( + foreign, drop_fk, n_drop_fk) + && NULL == dict_foreign_find_index( + indexed_table, col_names, + foreign->foreign_col_names, + foreign->n_fields, index, + /*check_charsets=*/TRUE, + /*check_null=*/FALSE, + NULL, NULL, NULL) + && NULL == innobase_find_equiv_index( + foreign->foreign_col_names, + foreign->n_fields, + ha_alter_info->key_info_buffer, + span<uint>(ha_alter_info->index_add_buffer, + ha_alter_info->index_add_count))) { + + /* Index cannot be dropped. */ + trx->error_info = index; + return(true); + } + } + + return(false); +} + +/** +Rename a given index in the InnoDB data dictionary. + +@param index index to rename +@param new_name new name of the index +@param[in,out] trx dict transaction to use, not going to be committed here + +@retval true Failure +@retval false Success */ +static MY_ATTRIBUTE((warn_unused_result)) +bool +rename_index_try( + const dict_index_t* index, + const char* new_name, + trx_t* trx) +{ + DBUG_ENTER("rename_index_try"); + ut_ad(dict_sys.locked()); + ut_ad(trx->dict_operation_lock_mode); + + pars_info_t* pinfo; + dberr_t err; + + pinfo = pars_info_create(); + + pars_info_add_ull_literal(pinfo, "table_id", index->table->id); + pars_info_add_ull_literal(pinfo, "index_id", index->id); + pars_info_add_str_literal(pinfo, "new_name", new_name); + + trx->op_info = "Renaming an index in SYS_INDEXES"; + + DBUG_EXECUTE_IF( + "ib_rename_index_fail1", + DBUG_SET("+d,innodb_report_deadlock"); + ); + + err = que_eval_sql( + pinfo, + "PROCEDURE RENAME_INDEX_IN_SYS_INDEXES () IS\n" + "BEGIN\n" + "UPDATE SYS_INDEXES SET\n" + "NAME = :new_name\n" + "WHERE\n" + "ID = :index_id AND\n" + "TABLE_ID = :table_id;\n" + "END;\n", trx); /* pinfo is freed by que_eval_sql() */ + + DBUG_EXECUTE_IF( + "ib_rename_index_fail1", + DBUG_SET("-d,innodb_report_deadlock"); + ); + + trx->op_info = ""; + + if (err != DB_SUCCESS) { + my_error_innodb(err, index->table->name.m_name, 0); + DBUG_RETURN(true); + } + + DBUG_RETURN(false); +} + + +/** +Rename a given index in the InnoDB data dictionary cache. + +@param[in,out] index index to rename +@param new_name new index name +*/ +static +void +innobase_rename_index_cache(dict_index_t* index, const char* new_name) +{ + DBUG_ENTER("innobase_rename_index_cache"); + ut_ad(dict_sys.locked()); + + size_t old_name_len = strlen(index->name); + size_t new_name_len = strlen(new_name); + + if (old_name_len < new_name_len) { + index->name = static_cast<char*>( + mem_heap_alloc(index->heap, new_name_len + 1)); + } + + memcpy(const_cast<char*>(index->name()), new_name, new_name_len + 1); + + DBUG_VOID_RETURN; +} + + +/** Rename the index name in cache. +@param[in] ctx alter context +@param[in] ha_alter_info Data used during inplace alter. */ +static void +innobase_rename_indexes_cache(const ha_innobase_inplace_ctx *ctx, + const Alter_inplace_info *ha_alter_info) +{ + DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_RENAME_INDEX); + + std::vector<std::pair<dict_index_t *, const char *>> rename_info; + rename_info.reserve(ha_alter_info->rename_keys.size()); + + for (const Alter_inplace_info::Rename_key_pair &pair : + ha_alter_info->rename_keys) + { + dict_index_t *index= + dict_table_get_index_on_name(ctx->old_table, pair.old_key->name.str); + ut_ad(index); + + rename_info.emplace_back(index, pair.new_key->name.str); + } + + for (const auto &pair : rename_info) + innobase_rename_index_cache(pair.first, pair.second); +} + +/** Fill the stored column information in s_cols list. +@param[in] altered_table mysql table object +@param[in] table innodb table object +@param[out] s_cols list of stored column +@param[out] s_heap heap for storing stored +column information. */ +static +void +alter_fill_stored_column( + const TABLE* altered_table, + dict_table_t* table, + dict_s_col_list** s_cols, + mem_heap_t** s_heap) +{ + ulint n_cols = altered_table->s->fields; + ulint stored_col_no = 0; + + for (ulint i = 0; i < n_cols; i++) { + Field* field = altered_table->field[i]; + dict_s_col_t s_col; + + if (field->stored_in_db()) { + stored_col_no++; + } + + if (!innobase_is_s_fld(field)) { + continue; + } + + ulint num_base = 0; + dict_col_t* col = dict_table_get_nth_col(table, + stored_col_no); + + s_col.m_col = col; + s_col.s_pos = i; + + if (*s_cols == NULL) { + *s_cols = UT_NEW_NOKEY(dict_s_col_list()); + *s_heap = mem_heap_create(1000); + } + + if (num_base != 0) { + s_col.base_col = static_cast<dict_col_t**>(mem_heap_zalloc( + *s_heap, num_base * sizeof(dict_col_t*))); + } else { + s_col.base_col = NULL; + } + + s_col.num_base = num_base; + innodb_base_col_setup_for_stored(table, field, &s_col); + (*s_cols)->push_front(s_col); + } +} + +static bool alter_templ_needs_rebuild(const TABLE* altered_table, + const Alter_inplace_info* ha_alter_info, + const dict_table_t* table); + +/** Check whether the column is present in table foreign key +relations. +@param table table which has foreign key relation +@param col column to be checked +@param col_name column name to be display during error +@param drop_fk Drop foreign key constraint +@param n_drop_fk number of drop foreign keys +@param add_fk Newly added foreign key constraint +@param n_add_fk number of newly added foreign constraint */ +static +bool check_col_is_in_fk_indexes( + const dict_table_t *table, const dict_col_t *col, + const char* col_name, + span<const dict_foreign_t *> drop_fk, + span<const dict_foreign_t *> add_fk) +{ + char *fk_id= nullptr; + + for (const auto &f : table->foreign_set) + { + if (!f->foreign_index || + std::find(drop_fk.begin(), drop_fk.end(), f) != drop_fk.end()) + continue; + for (ulint i= 0; i < f->n_fields; i++) + if (f->foreign_index->fields[i].col == col) + { + fk_id= f->id; + goto err_exit; + } + } + + for (const auto &a : add_fk) + { + for (ulint i= 0; i < a->n_fields; i++) + { + if (a->foreign_index->fields[i].col == col) + { + fk_id= a->id; + goto err_exit; + } + } + } + + for (const auto &f : table->referenced_set) + { + if (!f->referenced_index) continue; + for (ulint i= 0; i < f->n_fields; i++) + { + if (f->referenced_index->fields[i].col == col) + { + my_error(ER_FK_COLUMN_CANNOT_CHANGE_CHILD, MYF(0), + col_name, f->id, f->foreign_table_name); + return true; + } + } + } + return false; +err_exit: + my_error(ER_FK_COLUMN_CANNOT_CHANGE, MYF(0), col_name, + fk_id ? fk_id : + (std::string(table->name.m_name) + "_ibfk_0").c_str()); + return true; +} + +/** Allows InnoDB to update internal structures with concurrent +writes blocked (provided that check_if_supported_inplace_alter() +did not return HA_ALTER_INPLACE_NO_LOCK). +This will be invoked before inplace_alter_table(). + +@param altered_table TABLE object for new version of table. +@param ha_alter_info Structure describing changes to be done +by ALTER TABLE and holding data used during in-place alter. + +@retval true Failure +@retval false Success +*/ + +bool +ha_innobase::prepare_inplace_alter_table( +/*=====================================*/ + TABLE* altered_table, + Alter_inplace_info* ha_alter_info) +{ + dict_index_t** drop_index; /*!< Index to be dropped */ + ulint n_drop_index; /*!< Number of indexes to drop */ + dict_foreign_t**drop_fk; /*!< Foreign key constraints to drop */ + ulint n_drop_fk; /*!< Number of foreign keys to drop */ + dict_foreign_t**add_fk = NULL; /*!< Foreign key constraints to drop */ + ulint n_add_fk= 0; /*!< Number of foreign keys to drop */ + dict_table_t* indexed_table; /*!< Table where indexes are created */ + mem_heap_t* heap; + const char** col_names; + int error; + ulint add_autoinc_col_no = ULINT_UNDEFINED; + ulonglong autoinc_col_max_value = 0; + ulint fts_doc_col_no = ULINT_UNDEFINED; + bool add_fts_doc_id = false; + bool add_fts_doc_id_idx = false; + bool add_fts_idx = false; + dict_s_col_list*s_cols = NULL; + mem_heap_t* s_heap = NULL; + + DBUG_ENTER("prepare_inplace_alter_table"); + DBUG_ASSERT(!ha_alter_info->handler_ctx); + DBUG_ASSERT(ha_alter_info->create_info); + DBUG_ASSERT(!srv_read_only_mode); + + /* Init online ddl status variables */ + onlineddl_rowlog_rows = 0; + onlineddl_rowlog_pct_used = 0; + onlineddl_pct_progress = 0; + + MONITOR_ATOMIC_INC(MONITOR_PENDING_ALTER_TABLE); + +#ifdef UNIV_DEBUG + for (dict_index_t* index = dict_table_get_first_index(m_prebuilt->table); + index; + index = dict_table_get_next_index(index)) { + ut_ad(!index->to_be_dropped); + } +#endif /* UNIV_DEBUG */ + + ut_d(dict_sys.freeze(SRW_LOCK_CALL)); + ut_d(dict_table_check_for_dup_indexes( + m_prebuilt->table, CHECK_ABORTED_OK)); + ut_d(dict_sys.unfreeze()); + + if (!(ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE)) { + /* Nothing to do */ + DBUG_ASSERT(!m_prebuilt->trx->dict_operation_lock_mode); + m_prebuilt->trx_id = 0; + DBUG_RETURN(false); + } + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->part_info == NULL) { +#endif + /* Ignore the MDL downgrade when table is empty. + This optimization is disabled for partition table. */ + ha_alter_info->mdl_exclusive_after_prepare = + innobase_table_is_empty(m_prebuilt->table, false); + if (ha_alter_info->online + && ha_alter_info->mdl_exclusive_after_prepare) { + ha_alter_info->online = false; + } +#ifdef WITH_PARTITION_STORAGE_ENGINE + } +#endif + indexed_table = m_prebuilt->table; + + /* ALTER TABLE will not implicitly move a table from a single-table + tablespace to the system tablespace when innodb_file_per_table=OFF. + But it will implicitly move a table from the system tablespace to a + single-table tablespace if innodb_file_per_table = ON. */ + + create_table_info_t info(m_user_thd, + altered_table, + ha_alter_info->create_info, + NULL, + NULL, + srv_file_per_table); + + info.set_tablespace_type(indexed_table->space != fil_system.sys_space); + + if (ha_alter_info->handler_flags & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) { + if (info.gcols_in_fulltext_or_spatial()) { + goto err_exit_no_heap; + } + } + + if (indexed_table->is_readable()) { + } else { + if (indexed_table->corrupted) { + /* Handled below */ + } else { + if (const fil_space_t* space = indexed_table->space) { + String str; + const char* engine= table_type(); + + push_warning_printf( + m_user_thd, + Sql_condition::WARN_LEVEL_WARN, + HA_ERR_DECRYPTION_FAILED, + "Table %s in file %s is encrypted but encryption service or" + " used key_id is not available. " + " Can't continue reading table.", + table_share->table_name.str, + space->chain.start->name); + + my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_DECRYPTION_FAILED, str.c_ptr(), engine); + DBUG_RETURN(true); + } + } + } + + if (indexed_table->corrupted + || dict_table_get_first_index(indexed_table) == NULL + || dict_table_get_first_index(indexed_table)->is_corrupted()) { + /* The clustered index is corrupted. */ + my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0)); + DBUG_RETURN(true); + } else { + const char* invalid_opt = info.create_options_are_invalid(); + + /* Check engine specific table options */ + if (const char* invalid_tbopt = info.check_table_options()) { + my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), + table_type(), invalid_tbopt); + goto err_exit_no_heap; + } + + if (invalid_opt) { + my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), + table_type(), invalid_opt); + goto err_exit_no_heap; + } + } + + /* Check if any index name is reserved. */ + if (innobase_index_name_is_reserved( + m_user_thd, + ha_alter_info->key_info_buffer, + ha_alter_info->key_count)) { +err_exit_no_heap: + DBUG_ASSERT(!m_prebuilt->trx->dict_operation_lock_mode); + online_retry_drop_indexes(m_prebuilt->table, m_user_thd); + DBUG_RETURN(true); + } + + indexed_table = m_prebuilt->table; + + /* Check that index keys are sensible */ + error = innobase_check_index_keys(ha_alter_info, indexed_table); + + if (error) { + goto err_exit_no_heap; + } + + /* Prohibit renaming a column to something that the table + already contains. */ + if (ha_alter_info->handler_flags + & ALTER_COLUMN_NAME) { + for (Field** fp = table->field; *fp; fp++) { + if (!((*fp)->flags & FIELD_IS_RENAMED)) { + continue; + } + + const char* name = 0; + + for (const Create_field& cf : + ha_alter_info->alter_info->create_list) { + if (cf.field == *fp) { + name = cf.field_name.str; + goto check_if_ok_to_rename; + } + } + + ut_error; +check_if_ok_to_rename: + /* Prohibit renaming a column from FTS_DOC_ID + if full-text indexes exist. */ + if (!my_strcasecmp(system_charset_info, + (*fp)->field_name.str, + FTS_DOC_ID_COL_NAME) + && innobase_fulltext_exist(altered_table)) { + my_error(ER_INNODB_FT_WRONG_DOCID_COLUMN, + MYF(0), name); + goto err_exit_no_heap; + } + + /* Prohibit renaming a column to an internal column. */ + const char* s = m_prebuilt->table->col_names; + unsigned j; + /* Skip user columns. + MySQL should have checked these already. + We want to allow renaming of c1 to c2, c2 to c1. */ + for (j = 0; j < table->s->fields; j++) { + if (table->field[j]->stored_in_db()) { + s += strlen(s) + 1; + } + } + + for (; j < m_prebuilt->table->n_def; j++) { + if (!my_strcasecmp( + system_charset_info, name, s)) { + my_error(ER_WRONG_COLUMN_NAME, MYF(0), + s); + goto err_exit_no_heap; + } + + s += strlen(s) + 1; + } + } + } + + if (!info.innobase_table_flags()) { + my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), + table_type(), "PAGE_COMPRESSED"); + goto err_exit_no_heap; + } + + if (info.flags2() & DICT_TF2_USE_FILE_PER_TABLE) { + /* Preserve the DATA DIRECTORY attribute, because it + currently cannot be changed during ALTER TABLE. */ + info.flags_set(m_prebuilt->table->flags + & 1U << DICT_TF_POS_DATA_DIR); + } + + + /* ALGORITHM=INPLACE without rebuild (10.3+ ALGORITHM=NOCOPY) + must use the current ROW_FORMAT of the table. */ + const ulint max_col_len = DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG( + innobase_need_rebuild(ha_alter_info, this->table) + ? info.flags() + : m_prebuilt->table->flags); + + /* Check each index's column length to make sure they do not + exceed limit */ + for (ulint i = 0; i < ha_alter_info->key_count; i++) { + const KEY* key = &ha_alter_info->key_info_buffer[i]; + + if (key->flags & HA_FULLTEXT) { + /* The column length does not matter for + fulltext search indexes. But, UNIQUE + fulltext indexes are not supported. */ + DBUG_ASSERT(!(key->flags & HA_NOSAME)); + DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK + & ~(HA_FULLTEXT + | HA_PACK_KEY + | HA_BINARY_PACK_KEY))); + add_fts_idx = true; + continue; + } + + if (too_big_key_part_length(max_col_len, *key)) { + my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), + max_col_len); + goto err_exit_no_heap; + } + } + + /* We won't be allowed to add fts index to a table with + fts indexes already but without AUX_HEX_NAME set. + This means the aux tables of the table failed to + rename to hex format but new created aux tables + shall be in hex format, which is contradictory. */ + if (!DICT_TF2_FLAG_IS_SET(indexed_table, DICT_TF2_FTS_AUX_HEX_NAME) + && indexed_table->fts != NULL && add_fts_idx) { + my_error(ER_INNODB_FT_AUX_NOT_HEX_ID, MYF(0)); + goto err_exit_no_heap; + } + + /* Check existing index definitions for too-long column + prefixes as well, in case max_col_len shrunk. */ + for (const dict_index_t* index + = dict_table_get_first_index(indexed_table); + index; + index = dict_table_get_next_index(index)) { + if (index->type & DICT_FTS) { + DBUG_ASSERT(index->type == DICT_FTS + || (index->type & DICT_CORRUPT)); + + /* We need to drop any corrupted fts indexes + before we add a new fts index. */ + if (add_fts_idx && index->type & DICT_CORRUPT) { + ib_errf(m_user_thd, IB_LOG_LEVEL_ERROR, + ER_INNODB_INDEX_CORRUPT, + "Fulltext index '%s' is corrupt. " + "you should drop this index first.", + index->name()); + + goto err_exit_no_heap; + } + + continue; + } + + for (ulint i = 0; i < dict_index_get_n_fields(index); i++) { + const dict_field_t* field + = dict_index_get_nth_field(index, i); + if (field->prefix_len > max_col_len) { + my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), + max_col_len); + goto err_exit_no_heap; + } + } + } + + n_drop_index = 0; + n_drop_fk = 0; + + if (ha_alter_info->handler_flags + & (INNOBASE_ALTER_NOREBUILD | INNOBASE_ALTER_REBUILD + | INNOBASE_ALTER_INSTANT)) { + heap = mem_heap_create(1024); + + if (ha_alter_info->handler_flags + & ALTER_COLUMN_NAME) { + col_names = innobase_get_col_names( + ha_alter_info, altered_table, table, + indexed_table, heap); + } else { + col_names = NULL; + } + } else { + heap = NULL; + col_names = NULL; + } + + if (ha_alter_info->handler_flags + & ALTER_DROP_FOREIGN_KEY) { + DBUG_ASSERT(ha_alter_info->alter_info->drop_list.elements > 0); + + drop_fk = static_cast<dict_foreign_t**>( + mem_heap_alloc( + heap, + ha_alter_info->alter_info->drop_list.elements + * sizeof(dict_foreign_t*))); + + for (Alter_drop& drop : ha_alter_info->alter_info->drop_list) { + if (drop.type != Alter_drop::FOREIGN_KEY) { + continue; + } + + dict_foreign_t* foreign; + + for (dict_foreign_set::iterator it + = m_prebuilt->table->foreign_set.begin(); + it != m_prebuilt->table->foreign_set.end(); + ++it) { + + foreign = *it; + const char* fid = strchr(foreign->id, '/'); + + DBUG_ASSERT(fid); + /* If no database/ prefix was present in + the FOREIGN KEY constraint name, compare + to the full constraint name. */ + fid = fid ? fid + 1 : foreign->id; + + if (!my_strcasecmp(system_charset_info, + fid, drop.name)) { + goto found_fk; + } + } + + my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), + drop.type_name(), drop.name); + goto err_exit; +found_fk: + for (ulint i = n_drop_fk; i--; ) { + if (drop_fk[i] == foreign) { + goto dup_fk; + } + } + drop_fk[n_drop_fk++] = foreign; +dup_fk: + continue; + } + + DBUG_ASSERT(n_drop_fk > 0); + + DBUG_ASSERT(n_drop_fk + <= ha_alter_info->alter_info->drop_list.elements); + } else { + drop_fk = NULL; + } + + if (ha_alter_info->index_drop_count) { + dict_index_t* drop_primary = NULL; + + DBUG_ASSERT(ha_alter_info->handler_flags + & (ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX + | ALTER_DROP_UNIQUE_INDEX + | ALTER_DROP_PK_INDEX)); + /* Check which indexes to drop. */ + drop_index = static_cast<dict_index_t**>( + mem_heap_alloc( + heap, (ha_alter_info->index_drop_count + 1) + * sizeof *drop_index)); + + for (uint i = 0; i < ha_alter_info->index_drop_count; i++) { + const KEY* key + = ha_alter_info->index_drop_buffer[i]; + dict_index_t* index + = dict_table_get_index_on_name( + indexed_table, key->name.str); + + if (!index) { + push_warning_printf( + m_user_thd, + Sql_condition::WARN_LEVEL_WARN, + HA_ERR_WRONG_INDEX, + "InnoDB could not find key" + " with name %s", key->name.str); + } else { + ut_ad(!index->to_be_dropped); + if (!index->is_primary()) { + drop_index[n_drop_index++] = index; + } else { + drop_primary = index; + } + } + } + + /* If all FULLTEXT indexes were removed, drop an + internal FTS_DOC_ID_INDEX as well, unless it exists in + the table. */ + + if (innobase_fulltext_exist(table) + && !innobase_fulltext_exist(altered_table) + && !DICT_TF2_FLAG_IS_SET( + indexed_table, DICT_TF2_FTS_HAS_DOC_ID)) { + dict_index_t* fts_doc_index + = indexed_table->fts_doc_id_index; + ut_ad(fts_doc_index); + + // Add some fault tolerance for non-debug builds. + if (fts_doc_index == NULL) { + goto check_if_can_drop_indexes; + } + + DBUG_ASSERT(!fts_doc_index->to_be_dropped); + + for (uint i = 0; i < table->s->keys; i++) { + if (!my_strcasecmp( + system_charset_info, + FTS_DOC_ID_INDEX_NAME, + table->key_info[i].name.str)) { + /* The index exists in the MySQL + data dictionary. Do not drop it, + even though it is no longer needed + by InnoDB fulltext search. */ + goto check_if_can_drop_indexes; + } + } + + drop_index[n_drop_index++] = fts_doc_index; + } + +check_if_can_drop_indexes: + /* Check if the indexes can be dropped. */ + + /* Prevent a race condition between DROP INDEX and + CREATE TABLE adding FOREIGN KEY constraints. */ + row_mysql_lock_data_dictionary(m_prebuilt->trx); + + if (!n_drop_index) { + drop_index = NULL; + } else { + /* Flag all indexes that are to be dropped. */ + for (ulint i = 0; i < n_drop_index; i++) { + ut_ad(!drop_index[i]->to_be_dropped); + drop_index[i]->to_be_dropped = 1; + } + } + + if (m_prebuilt->trx->check_foreigns) { + for (uint i = 0; i < n_drop_index; i++) { + dict_index_t* index = drop_index[i]; + + if (innobase_check_foreign_key_index( + ha_alter_info, index, + indexed_table, col_names, + m_prebuilt->trx, drop_fk, n_drop_fk)) { + row_mysql_unlock_data_dictionary( + m_prebuilt->trx); + m_prebuilt->trx->error_info = index; + print_error(HA_ERR_DROP_INDEX_FK, + MYF(0)); + goto err_exit; + } + } + + /* If a primary index is dropped, need to check + any depending foreign constraints get affected */ + if (drop_primary + && innobase_check_foreign_key_index( + ha_alter_info, drop_primary, + indexed_table, col_names, + m_prebuilt->trx, drop_fk, n_drop_fk)) { + row_mysql_unlock_data_dictionary(m_prebuilt->trx); + print_error(HA_ERR_DROP_INDEX_FK, MYF(0)); + goto err_exit; + } + } + + row_mysql_unlock_data_dictionary(m_prebuilt->trx); + } else { + drop_index = NULL; + } + + /* Check if any of the existing indexes are marked as corruption + and if they are, refuse adding more indexes. */ + if (ha_alter_info->handler_flags & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) { + for (dict_index_t* index = dict_table_get_first_index(indexed_table); + index != NULL; index = dict_table_get_next_index(index)) { + + if (!index->to_be_dropped && index->is_committed() + && index->is_corrupted()) { + my_error(ER_INDEX_CORRUPT, MYF(0), index->name()); + goto err_exit; + } + } + } + + if (ha_alter_info->handler_flags + & ALTER_ADD_FOREIGN_KEY) { + ut_ad(!m_prebuilt->trx->check_foreigns); + + alter_fill_stored_column(altered_table, m_prebuilt->table, + &s_cols, &s_heap); + + add_fk = static_cast<dict_foreign_t**>( + mem_heap_zalloc( + heap, + ha_alter_info->alter_info->key_list.elements + * sizeof(dict_foreign_t*))); + + if (!innobase_get_foreign_key_info( + ha_alter_info, table_share, + m_prebuilt->table, col_names, + drop_index, n_drop_index, + add_fk, &n_add_fk, m_prebuilt->trx, s_cols)) { +err_exit: + if (n_drop_index) { + row_mysql_lock_data_dictionary(m_prebuilt->trx); + + /* Clear the to_be_dropped flags, which might + have been set at this point. */ + for (ulint i = 0; i < n_drop_index; i++) { + ut_ad(drop_index[i]->is_committed()); + drop_index[i]->to_be_dropped = 0; + } + + row_mysql_unlock_data_dictionary( + m_prebuilt->trx); + } + + for (uint i = 0; i < n_add_fk; i++) { + if (add_fk[i]) { + dict_foreign_free(add_fk[i]); + } + } + + if (heap) { + mem_heap_free(heap); + } + + if (s_cols != NULL) { + UT_DELETE(s_cols); + mem_heap_free(s_heap); + } + + goto err_exit_no_heap; + } + + if (s_cols != NULL) { + UT_DELETE(s_cols); + mem_heap_free(s_heap); + } + } + + /** Alter shouldn't support if the foreign and referenced + index columns are modified */ + if (ha_alter_info->handler_flags + & ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE) { + + for (uint i= 0, n_v_col= 0; i < table->s->fields; + i++) { + Field* field = table->field[i]; + + /* Altering the virtual column is not + supported for inplace alter algorithm */ + if (field->vcol_info) { + n_v_col++; + continue; + } + + for (const Create_field& new_field : + ha_alter_info->alter_info->create_list) { + if (new_field.field == field) { + if (!field->is_equal(new_field)) { + goto field_changed; + } + break; + } + } + + continue; +field_changed: + const char* col_name= field->field_name.str; + dict_col_t *col= dict_table_get_nth_col( + m_prebuilt->table, i - n_v_col); + if (check_col_is_in_fk_indexes( + m_prebuilt->table, col, col_name, + span<const dict_foreign_t*>( + const_cast<const dict_foreign_t**>( + drop_fk), n_drop_fk), + span<const dict_foreign_t*>( + const_cast<const dict_foreign_t**>( + add_fk), n_add_fk))) + goto err_exit; + } + } + + if (ha_alter_info->handler_flags & ALTER_RENAME_INDEX) { + for (const Alter_inplace_info::Rename_key_pair& pair : + ha_alter_info->rename_keys) { + dict_index_t* index = dict_table_get_index_on_name( + indexed_table, pair.old_key->name.str); + + if (!index || index->is_corrupted()) { + my_error(ER_INDEX_CORRUPT, MYF(0), + index->name()); + goto err_exit; + } + } + } + + const ha_table_option_struct& alt_opt= + *ha_alter_info->create_info->option_struct; + + ha_innobase_inplace_ctx *ctx = NULL; + + if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA) + || ((ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE + | INNOBASE_ALTER_NOCREATE + | INNOBASE_ALTER_INSTANT)) + == ALTER_OPTIONS + && !alter_options_need_rebuild(ha_alter_info, table))) { + + DBUG_ASSERT(!m_prebuilt->trx->dict_operation_lock_mode); + online_retry_drop_indexes(m_prebuilt->table, m_user_thd); + + if (heap) { + ctx = new ha_innobase_inplace_ctx( + m_prebuilt, + drop_index, n_drop_index, + drop_fk, n_drop_fk, + add_fk, n_add_fk, + ha_alter_info->online, + heap, indexed_table, + col_names, ULINT_UNDEFINED, 0, 0, + (ha_alter_info->ignore + || !thd_is_strict_mode(m_user_thd)), + alt_opt.page_compressed, + alt_opt.page_compression_level); + ha_alter_info->handler_ctx = ctx; + } + + if ((ha_alter_info->handler_flags + & ALTER_DROP_VIRTUAL_COLUMN) + && prepare_inplace_drop_virtual(ha_alter_info, table)) { + DBUG_RETURN(true); + } + + if ((ha_alter_info->handler_flags + & ALTER_ADD_VIRTUAL_COLUMN) + && prepare_inplace_add_virtual( + ha_alter_info, altered_table, table)) { + DBUG_RETURN(true); + } + + if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA) + && alter_templ_needs_rebuild(altered_table, ha_alter_info, + ctx->new_table) + && ctx->new_table->n_v_cols > 0) { + /* Changing maria record structure may end up here only + if virtual columns were altered. In this case, however, + vc_templ should be rebuilt. Since we don't actually + change any stored data, we can just dispose vc_templ; + it will be recreated on next ha_innobase::open(). */ + + DBUG_ASSERT(ctx->new_table == ctx->old_table); + + dict_free_vc_templ(ctx->new_table->vc_templ); + UT_DELETE(ctx->new_table->vc_templ); + + ctx->new_table->vc_templ = NULL; + } + + +success: + /* Memorize the future transaction ID for committing + the data dictionary change, to be reported by + ha_innobase::table_version(). */ + m_prebuilt->trx_id = (ha_alter_info->handler_flags + & ~INNOBASE_INPLACE_IGNORE) + ? static_cast<ha_innobase_inplace_ctx*> + (ha_alter_info->handler_ctx)->trx->id + : 0; + DBUG_RETURN(false); + } + + /* If we are to build a full-text search index, check whether + the table already has a DOC ID column. If not, we will need to + add a Doc ID hidden column and rebuild the primary index */ + if (innobase_fulltext_exist(altered_table)) { + ulint doc_col_no; + ulint num_v = 0; + + if (!innobase_fts_check_doc_id_col( + m_prebuilt->table, + altered_table, &fts_doc_col_no, &num_v)) { + + fts_doc_col_no = altered_table->s->fields - num_v; + add_fts_doc_id = true; + add_fts_doc_id_idx = true; + + } else if (fts_doc_col_no == ULINT_UNDEFINED) { + goto err_exit; + } + + switch (innobase_fts_check_doc_id_index( + m_prebuilt->table, altered_table, + &doc_col_no)) { + case FTS_NOT_EXIST_DOC_ID_INDEX: + add_fts_doc_id_idx = true; + break; + case FTS_INCORRECT_DOC_ID_INDEX: + my_error(ER_INNODB_FT_WRONG_DOCID_INDEX, MYF(0), + FTS_DOC_ID_INDEX_NAME); + goto err_exit; + case FTS_EXIST_DOC_ID_INDEX: + DBUG_ASSERT( + doc_col_no == fts_doc_col_no + || doc_col_no == ULINT_UNDEFINED + || (ha_alter_info->handler_flags + & (ALTER_STORED_COLUMN_ORDER + | ALTER_DROP_STORED_COLUMN + | ALTER_ADD_STORED_BASE_COLUMN))); + } + } + + /* See if an AUTO_INCREMENT column was added. */ + uint i = 0; + ulint num_v = 0; + for (const Create_field& new_field : + ha_alter_info->alter_info->create_list) { + const Field* field; + + DBUG_ASSERT(i < altered_table->s->fields); + + for (uint old_i = 0; table->field[old_i]; old_i++) { + if (new_field.field == table->field[old_i]) { + goto found_col; + } + } + + /* This is an added column. */ + DBUG_ASSERT(!new_field.field); + DBUG_ASSERT(ha_alter_info->handler_flags + & ALTER_ADD_COLUMN); + + field = altered_table->field[i]; + + DBUG_ASSERT((field->unireg_check + == Field::NEXT_NUMBER) + == !!(field->flags & AUTO_INCREMENT_FLAG)); + + if (field->flags & AUTO_INCREMENT_FLAG) { + if (add_autoinc_col_no != ULINT_UNDEFINED) { + /* This should have been blocked earlier. */ + ut_ad(0); + my_error(ER_WRONG_AUTO_KEY, MYF(0)); + goto err_exit; + } + + /* Get the col no of the old table non-virtual column array */ + add_autoinc_col_no = i - num_v; + + autoinc_col_max_value = innobase_get_int_col_max_value(field); + } +found_col: + num_v += !new_field.stored_in_db(); + i++; + } + + DBUG_ASSERT(heap); + DBUG_ASSERT(m_user_thd == m_prebuilt->trx->mysql_thd); + DBUG_ASSERT(!ha_alter_info->handler_ctx); + + ha_alter_info->handler_ctx = new ha_innobase_inplace_ctx( + m_prebuilt, + drop_index, n_drop_index, + drop_fk, n_drop_fk, add_fk, n_add_fk, + ha_alter_info->online, + heap, m_prebuilt->table, col_names, + add_autoinc_col_no, + ha_alter_info->create_info->auto_increment_value, + autoinc_col_max_value, + ha_alter_info->ignore || !thd_is_strict_mode(m_user_thd), + alt_opt.page_compressed, alt_opt.page_compression_level); + + if (!prepare_inplace_alter_table_dict( + ha_alter_info, altered_table, table, + table_share->table_name.str, + info.flags(), info.flags2(), + fts_doc_col_no, add_fts_doc_id, + add_fts_doc_id_idx)) { + goto success; + } + + DBUG_RETURN(true); +} + +/* Check whether a columnn length change alter operation requires +to rebuild the template. +@param[in] altered_table TABLE object for new version of table. +@param[in] ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used + during in-place alter. +@param[in] table table being altered +@return TRUE if needs rebuild. */ +static +bool +alter_templ_needs_rebuild( + const TABLE* altered_table, + const Alter_inplace_info* ha_alter_info, + const dict_table_t* table) +{ + ulint i = 0; + + for (Field** fp = altered_table->field; *fp; fp++, i++) { + for (const Create_field& cf : + ha_alter_info->alter_info->create_list) { + for (ulint j=0; j < table->n_cols; j++) { + dict_col_t* cols + = dict_table_get_nth_col(table, j); + if (cf.length > cols->len) { + return(true); + } + } + } + } + + return(false); +} + +/** Alter the table structure in-place with operations +specified using Alter_inplace_info. +The level of concurrency allowed during this operation depends +on the return value from check_if_supported_inplace_alter(). + +@param altered_table TABLE object for new version of table. +@param ha_alter_info Structure describing changes to be done +by ALTER TABLE and holding data used during in-place alter. + +@retval true Failure +@retval false Success +*/ + +bool +ha_innobase::inplace_alter_table( +/*=============================*/ + TABLE* altered_table, + Alter_inplace_info* ha_alter_info) +{ + dberr_t error; + dict_add_v_col_t* add_v = NULL; + dict_vcol_templ_t* s_templ = NULL; + dict_vcol_templ_t* old_templ = NULL; + struct TABLE* eval_table = altered_table; + bool rebuild_templ = false; + DBUG_ENTER("inplace_alter_table"); + DBUG_ASSERT(!srv_read_only_mode); + + DEBUG_SYNC(m_user_thd, "innodb_inplace_alter_table_enter"); + + /* Ignore the inplace alter phase when table is empty */ + if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA) + || ha_alter_info->mdl_exclusive_after_prepare) { +ok_exit: + DEBUG_SYNC(m_user_thd, "innodb_after_inplace_alter_table"); + DBUG_RETURN(false); + } + + if ((ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE + | INNOBASE_ALTER_NOCREATE + | INNOBASE_ALTER_INSTANT)) + == ALTER_OPTIONS + && !alter_options_need_rebuild(ha_alter_info, table)) { + goto ok_exit; + } + + ha_innobase_inplace_ctx* ctx + = static_cast<ha_innobase_inplace_ctx*> + (ha_alter_info->handler_ctx); + + DBUG_ASSERT(ctx); + DBUG_ASSERT(ctx->trx); + DBUG_ASSERT(ctx->prebuilt == m_prebuilt); + + if (ctx->is_instant()) goto ok_exit; + + dict_index_t* pk = dict_table_get_first_index(m_prebuilt->table); + ut_ad(pk != NULL); + + /* For partitioned tables this could be already allocated from a + previous partition invocation. For normal tables this is NULL. */ + UT_DELETE(ctx->m_stage); + + ctx->m_stage = UT_NEW_NOKEY(ut_stage_alter_t(pk)); + + if (!m_prebuilt->table->is_readable()) { + goto all_done; + } + + /* If we are doing a table rebuilding or having added virtual + columns in the same clause, we will need to build a table template + that carries translation information between MySQL TABLE and InnoDB + table, which indicates the virtual columns and their base columns + info. This is used to do the computation callback, so that the + data in base columns can be extracted send to server. + If the Column length changes and it is a part of virtual + index then we need to rebuild the template. */ + rebuild_templ + = ctx->need_rebuild() + || ((ha_alter_info->handler_flags + & ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE) + && alter_templ_needs_rebuild( + altered_table, ha_alter_info, ctx->new_table)); + + if ((ctx->new_table->n_v_cols > 0) && rebuild_templ) { + /* Save the templ if isn't NULL so as to restore the + original state in case of alter operation failures. */ + if (ctx->new_table->vc_templ != NULL && !ctx->need_rebuild()) { + old_templ = ctx->new_table->vc_templ; + } + s_templ = UT_NEW_NOKEY(dict_vcol_templ_t()); + + innobase_build_v_templ( + altered_table, ctx->new_table, s_templ, NULL, false); + + ctx->new_table->vc_templ = s_templ; + } else if (ctx->num_to_add_vcol > 0 && ctx->num_to_drop_vcol == 0) { + /* if there is ongoing drop virtual column, then we disallow + inplace add index on newly added virtual column, so it does + not need to come in here to rebuild template with add_v. + Please also see the assertion in innodb_v_adjust_idx_col() */ + + s_templ = UT_NEW_NOKEY(dict_vcol_templ_t()); + + add_v = static_cast<dict_add_v_col_t*>( + mem_heap_alloc(ctx->heap, sizeof *add_v)); + add_v->n_v_col = ctx->num_to_add_vcol; + add_v->v_col = ctx->add_vcol; + add_v->v_col_name = ctx->add_vcol_name; + + innobase_build_v_templ( + altered_table, ctx->new_table, s_templ, add_v, false); + old_templ = ctx->new_table->vc_templ; + ctx->new_table->vc_templ = s_templ; + } + + /* Drop virtual column without rebuild will keep dict table + unchanged, we use old table to evaluate virtual column value + in innobase_get_computed_value(). */ + if (!ctx->need_rebuild() && ctx->num_to_drop_vcol > 0) { + eval_table = table; + } + + /* Read the clustered index of the table and build + indexes based on this information using temporary + files and merge sort. */ + DBUG_EXECUTE_IF("innodb_OOM_inplace_alter", + error = DB_OUT_OF_MEMORY; goto oom;); + + error = row_merge_build_indexes( + m_prebuilt->trx, + m_prebuilt->table, ctx->new_table, + ctx->online, + ctx->add_index, ctx->add_key_numbers, ctx->num_to_add_index, + altered_table, ctx->defaults, ctx->col_map, + ctx->add_autoinc, ctx->sequence, ctx->skip_pk_sort, + ctx->m_stage, add_v, eval_table, ctx->allow_not_null, + ctx->change_col_collate.empty() + ? nullptr : &ctx->change_col_collate); + +#ifndef DBUG_OFF +oom: +#endif /* !DBUG_OFF */ + if (error == DB_SUCCESS && ctx->online && ctx->need_rebuild()) { + DEBUG_SYNC_C("row_log_table_apply1_before"); + error = row_log_table_apply( + ctx->thr, m_prebuilt->table, altered_table, + ctx->m_stage, ctx->new_table); + } + + /* Init online ddl status variables */ + onlineddl_rowlog_rows = 0; + onlineddl_rowlog_pct_used = 0; + onlineddl_pct_progress = 0; + + if (s_templ) { + ut_ad(ctx->need_rebuild() || ctx->num_to_add_vcol > 0 + || rebuild_templ); + dict_free_vc_templ(s_templ); + UT_DELETE(s_templ); + + ctx->new_table->vc_templ = old_templ; + } + + DEBUG_SYNC_C("inplace_after_index_build"); + + DBUG_EXECUTE_IF("create_index_fail", + error = DB_DUPLICATE_KEY; + m_prebuilt->trx->error_key_num = ULINT_UNDEFINED;); + + /* After an error, remove all those index definitions + from the dictionary which were defined. */ + + switch (error) { + KEY* dup_key; + default: + my_error_innodb(error, + table_share->table_name.str, + m_prebuilt->table->flags); + break; + all_done: + case DB_SUCCESS: + ut_d(dict_sys.freeze(SRW_LOCK_CALL)); + ut_d(dict_table_check_for_dup_indexes( + m_prebuilt->table, CHECK_PARTIAL_OK)); + ut_d(dict_sys.unfreeze()); + /* prebuilt->table->n_ref_count can be anything here, + given that we hold at most a shared lock on the table. */ + goto ok_exit; + case DB_DUPLICATE_KEY: + if (m_prebuilt->trx->error_key_num == ULINT_UNDEFINED + || ha_alter_info->key_count == 0) { + /* This should be the hidden index on + FTS_DOC_ID, or there is no PRIMARY KEY in the + table. Either way, we should be seeing and + reporting a bogus duplicate key error. */ + dup_key = NULL; + } else { + DBUG_ASSERT(m_prebuilt->trx->error_key_num + < ha_alter_info->key_count); + dup_key = &ha_alter_info->key_info_buffer[ + m_prebuilt->trx->error_key_num]; + } + print_keydup_error(altered_table, dup_key, MYF(0)); + break; + case DB_ONLINE_LOG_TOO_BIG: + DBUG_ASSERT(ctx->online); + my_error(ER_INNODB_ONLINE_LOG_TOO_BIG, MYF(0), + get_error_key_name(m_prebuilt->trx->error_key_num, + ha_alter_info, m_prebuilt->table)); + break; + case DB_INDEX_CORRUPT: + my_error(ER_INDEX_CORRUPT, MYF(0), + get_error_key_name(m_prebuilt->trx->error_key_num, + ha_alter_info, m_prebuilt->table)); + break; + case DB_DECRYPTION_FAILED: + String str; + const char* engine= table_type(); + get_error_message(HA_ERR_DECRYPTION_FAILED, &str); + my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_DECRYPTION_FAILED, + str.c_ptr(), engine); + break; + } + + /* prebuilt->table->n_ref_count can be anything here, given + that we hold at most a shared lock on the table. */ + m_prebuilt->trx->error_info = NULL; + ctx->trx->error_state = DB_SUCCESS; + + DBUG_RETURN(true); +} + +/** Free the modification log for online table rebuild. +@param table table that was being rebuilt online */ +static +void +innobase_online_rebuild_log_free( +/*=============================*/ + dict_table_t* table) +{ + dict_index_t* clust_index = dict_table_get_first_index(table); + ut_ad(dict_sys.locked()); + clust_index->lock.x_lock(SRW_LOCK_CALL); + + if (clust_index->online_log) { + ut_ad(dict_index_get_online_status(clust_index) + == ONLINE_INDEX_CREATION); + clust_index->online_status = ONLINE_INDEX_COMPLETE; + row_log_free(clust_index->online_log); + clust_index->online_log = NULL; + DEBUG_SYNC_C("innodb_online_rebuild_log_free_aborted"); + } + + DBUG_ASSERT(dict_index_get_online_status(clust_index) + == ONLINE_INDEX_COMPLETE); + clust_index->lock.x_unlock(); +} + +/** For each user column, which is part of an index which is not going to be +dropped, it checks if the column number of the column is same as col_no +argument passed. +@param[in] table table +@param[in] col_no column number +@param[in] is_v if this is a virtual column +@param[in] only_committed whether to consider only committed indexes +@retval true column exists +@retval false column does not exist, true if column is system column or +it is in the index. */ +static +bool +check_col_exists_in_indexes( + const dict_table_t* table, + ulint col_no, + bool is_v, + bool only_committed = false) +{ + /* This function does not check system columns */ + if (!is_v && dict_table_get_nth_col(table, col_no)->mtype == DATA_SYS) { + return(true); + } + + for (const dict_index_t* index = dict_table_get_first_index(table); + index; + index = dict_table_get_next_index(index)) { + + if (only_committed + ? !index->is_committed() + : index->to_be_dropped) { + continue; + } + + for (ulint i = 0; i < index->n_user_defined_cols; i++) { + const dict_col_t* idx_col + = dict_index_get_nth_col(index, i); + + if (is_v && idx_col->is_virtual()) { + const dict_v_col_t* v_col = reinterpret_cast< + const dict_v_col_t*>(idx_col); + if (v_col->v_pos == col_no) { + return(true); + } + } + + if (!is_v && !idx_col->is_virtual() + && dict_col_get_no(idx_col) == col_no) { + return(true); + } + } + } + + return(false); +} + +/** Rollback a secondary index creation, drop the indexes with +temparary index prefix +@param user_table InnoDB table +@param table the TABLE +@param locked TRUE=table locked, FALSE=may need to do a lazy drop +@param trx the transaction +@param alter_trx transaction which takes S-lock on the table + while creating the index */ +static +void +innobase_rollback_sec_index( + dict_table_t* user_table, + const TABLE* table, + bool locked, + trx_t* trx, + const trx_t* alter_trx=NULL) +{ + row_merge_drop_indexes(trx, user_table, locked, alter_trx); + + /* Free the table->fts only if there is no FTS_DOC_ID + in the table */ + if (user_table->fts + && !DICT_TF2_FLAG_IS_SET(user_table, + DICT_TF2_FTS_HAS_DOC_ID) + && !innobase_fulltext_exist(table)) { + user_table->fts->~fts_t(); + user_table->fts = nullptr; + } +} + +MY_ATTRIBUTE((nonnull, warn_unused_result)) +/** Roll back the changes made during prepare_inplace_alter_table() +and inplace_alter_table() inside the storage engine. Note that the +allowed level of concurrency during this operation will be the same as +for inplace_alter_table() and thus might be higher than during +prepare_inplace_alter_table(). (E.g concurrent writes were blocked +during prepare, but might not be during commit). + +@param ha_alter_info Data used during in-place alter. +@param table the TABLE +@param prebuilt the prebuilt struct +@retval true Failure +@retval false Success +*/ +inline bool rollback_inplace_alter_table(Alter_inplace_info *ha_alter_info, + const TABLE *table, + row_prebuilt_t *prebuilt) +{ + bool fail= false; + ha_innobase_inplace_ctx *ctx= static_cast<ha_innobase_inplace_ctx*> + (ha_alter_info->handler_ctx); + + DBUG_ENTER("rollback_inplace_alter_table"); + + DEBUG_SYNC_C("innodb_rollback_inplace_alter_table"); + if (!ctx) + /* If we have not started a transaction yet, + (almost) nothing has been or needs to be done. */ + dict_sys.lock(SRW_LOCK_CALL); + else if (ctx->trx->state == TRX_STATE_NOT_STARTED) + goto free_and_exit; + else if (ctx->new_table) + { + ut_ad(ctx->trx->state == TRX_STATE_ACTIVE); + const bool fts_exist= (ctx->new_table->flags2 & + (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)) || + ctx->adding_fulltext_index(); + if (ctx->need_rebuild()) + { + if (fts_exist) + { + fts_optimize_remove_table(ctx->new_table); + purge_sys.stop_FTS(*ctx->new_table); + } + + dberr_t err= lock_table_for_trx(ctx->new_table, ctx->trx, LOCK_X); + if (fts_exist) + { + if (err == DB_SUCCESS) + err= fts_lock_common_tables(ctx->trx, *ctx->new_table); + for (const dict_index_t* index= ctx->new_table->indexes.start; + err == DB_SUCCESS && index; index= index->indexes.next) + if (index->type & DICT_FTS) + err= fts_lock_index_tables(ctx->trx, *index); + } + if (err == DB_SUCCESS) + err= lock_sys_tables(ctx->trx); + + row_mysql_lock_data_dictionary(ctx->trx); + /* Detach ctx->new_table from dict_index_t::online_log. */ + innobase_online_rebuild_log_free(ctx->old_table); + + ut_d(const bool last_handle=) ctx->new_table->release(); + ut_ad(last_handle); + if (err == DB_SUCCESS) + err= ctx->trx->drop_table(*ctx->new_table); + + if (err == DB_SUCCESS) + for (const dict_index_t* index= ctx->new_table->indexes.start; index; + index= index->indexes.next) + if (index->type & DICT_FTS) + if (dberr_t err2= fts_drop_index_tables(ctx->trx, *index)) + err= err2; + + if (err != DB_SUCCESS) + { + my_error_innodb(err, table->s->table_name.str, ctx->new_table->flags); + fail= true; + } + } + else + { + DBUG_ASSERT(!(ha_alter_info->handler_flags & ALTER_ADD_PK_INDEX)); + DBUG_ASSERT(ctx->old_table == prebuilt->table); + uint &innodb_lock_wait_timeout= + thd_lock_wait_timeout(ctx->trx->mysql_thd); + const uint save_timeout= innodb_lock_wait_timeout; + innodb_lock_wait_timeout= ~0U; /* infinite */ + dict_index_t *old_clust_index= ctx->old_table->indexes.start; + old_clust_index->lock.x_lock(SRW_LOCK_CALL); + old_clust_index->online_log= nullptr; + old_clust_index->lock.x_unlock(); + if (fts_exist) + { + const dict_index_t *fts_index= nullptr; + for (ulint a= 0; a < ctx->num_to_add_index; a++) + { + const dict_index_t *index = ctx->add_index[a]; + if (index->type & DICT_FTS) + fts_index= index; + } + + /* Remove the fts table from fts_optimize_wq if there are + no FTS secondary index exist other than newly added one */ + if (fts_index && + (ib_vector_is_empty(prebuilt->table->fts->indexes) || + (ib_vector_size(prebuilt->table->fts->indexes) == 1 && + fts_index == static_cast<dict_index_t*>( + ib_vector_getp(prebuilt->table->fts->indexes, 0))))) + fts_optimize_remove_table(prebuilt->table); + + purge_sys.stop_FTS(*prebuilt->table); + ut_a(!fts_index || !fts_lock_index_tables(ctx->trx, *fts_index)); + ut_a(!fts_lock_common_tables(ctx->trx, *ctx->new_table)); + ut_a(!lock_sys_tables(ctx->trx)); + } + else + { + ut_a(!lock_table_for_trx(dict_sys.sys_indexes, ctx->trx, LOCK_X)); + ut_a(!lock_table_for_trx(dict_sys.sys_fields, ctx->trx, LOCK_X)); + } + innodb_lock_wait_timeout= save_timeout; + DEBUG_SYNC_C("innodb_rollback_after_fts_lock"); + row_mysql_lock_data_dictionary(ctx->trx); + ctx->rollback_instant(); + innobase_rollback_sec_index(ctx->old_table, table, + ha_alter_info->alter_info->requested_lock == + Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE, + ctx->trx, prebuilt->trx); + ctx->clean_new_vcol_index(); + ctx->cleanup_col_collation(); + ut_d(dict_table_check_for_dup_indexes(ctx->old_table, CHECK_ABORTED_OK)); + } + + DEBUG_SYNC(ctx->trx->mysql_thd, "before_commit_rollback_inplace"); + commit_unlock_and_unlink(ctx->trx); + if (fts_exist) + purge_sys.resume_FTS(); + if (ctx->old_table->fts) + { + dict_sys.lock(SRW_LOCK_CALL); + ut_ad(fts_check_cached_index(ctx->old_table)); + fts_optimize_add_table(ctx->old_table); + dict_sys.unlock(); + } + goto free_and_exit; + } + else + { +free_and_exit: + DBUG_ASSERT(ctx->prebuilt == prebuilt); + ctx->trx->free(); + ctx->trx= nullptr; + + dict_sys.lock(SRW_LOCK_CALL); + + if (ctx->add_vcol) + { + for (ulint i = 0; i < ctx->num_to_add_vcol; i++) + ctx->add_vcol[i].~dict_v_col_t(); + ctx->num_to_add_vcol= 0; + ctx->add_vcol= nullptr; + } + + for (ulint i= 0; i < ctx->num_to_add_fk; i++) + dict_foreign_free(ctx->add_fk[i]); + /* Clear the to_be_dropped flags in the data dictionary cache. + The flags may already have been cleared, in case an error was + detected in commit_inplace_alter_table(). */ + for (ulint i= 0; i < ctx->num_to_drop_index; i++) + { + dict_index_t *index= ctx->drop_index[i]; + DBUG_ASSERT(index->is_committed()); + index->to_be_dropped= 0; + } + } + + DBUG_ASSERT(!prebuilt->table->indexes.start->online_log); + DBUG_ASSERT(prebuilt->table->indexes.start->online_status == + ONLINE_INDEX_COMPLETE); + + /* Reset dict_col_t::ord_part for unindexed columns */ + for (ulint i= 0; i < dict_table_get_n_cols(prebuilt->table); i++) + { + dict_col_t &col= prebuilt->table->cols[i]; + if (col.ord_part && !check_col_exists_in_indexes(prebuilt->table, i, false, + true)) + col.ord_part= 0; + } + + for (ulint i = 0; i < dict_table_get_n_v_cols(prebuilt->table); i++) + { + dict_col_t &col = prebuilt->table->v_cols[i].m_col; + if (col.ord_part && !check_col_exists_in_indexes(prebuilt->table, i, true, + true)) + col.ord_part= 0; + } + dict_sys.unlock(); + trx_commit_for_mysql(prebuilt->trx); + prebuilt->trx_id = 0; + MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE); + DBUG_RETURN(fail); +} + +/** Drop a FOREIGN KEY constraint from the data dictionary tables. +@param trx data dictionary transaction +@param table_name Table name in MySQL +@param foreign_id Foreign key constraint identifier +@retval true Failure +@retval false Success */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_drop_foreign_try( +/*======================*/ + trx_t* trx, + const char* table_name, + const char* foreign_id) +{ + DBUG_ENTER("innobase_drop_foreign_try"); + + DBUG_ASSERT(trx->dict_operation); + ut_ad(trx->dict_operation_lock_mode); + ut_ad(dict_sys.locked()); + + /* Drop the constraint from the data dictionary. */ + static const char sql[] = + "PROCEDURE DROP_FOREIGN_PROC () IS\n" + "BEGIN\n" + "DELETE FROM SYS_FOREIGN WHERE ID=:id;\n" + "DELETE FROM SYS_FOREIGN_COLS WHERE ID=:id;\n" + "END;\n"; + + dberr_t error; + pars_info_t* info; + + info = pars_info_create(); + pars_info_add_str_literal(info, "id", foreign_id); + + trx->op_info = "dropping foreign key constraint from dictionary"; + error = que_eval_sql(info, sql, trx); + trx->op_info = ""; + + DBUG_EXECUTE_IF("ib_drop_foreign_error", + error = DB_OUT_OF_FILE_SPACE;); + + if (error != DB_SUCCESS) { + my_error_innodb(error, table_name, 0); + trx->error_state = DB_SUCCESS; + DBUG_RETURN(true); + } + + DBUG_RETURN(false); +} + +/** Rename a column in the data dictionary tables. +@param[in] ctx ALTER TABLE context +@param[in,out] trx Data dictionary transaction +@param[in] table_name Table name in MySQL +@param[in] from old column name +@param[in] to new column name +@retval true Failure +@retval false Success */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_rename_column_try( + const ha_innobase_inplace_ctx& ctx, + trx_t* trx, + const char* table_name, + const char* from, + const char* to) +{ + dberr_t error; + bool clust_has_wide_format = false; + + DBUG_ENTER("innobase_rename_column_try"); + + DBUG_ASSERT(trx->dict_operation); + ut_ad(trx->dict_operation_lock_mode); + ut_ad(dict_sys.locked()); + + if (ctx.need_rebuild()) { + goto rename_foreign; + } + + error = DB_SUCCESS; + + trx->op_info = "renaming column in SYS_FIELDS"; + + for (const dict_index_t* index = dict_table_get_first_index( + ctx.old_table); + index != NULL; + index = dict_table_get_next_index(index)) { + + bool wide_format = false; + for (size_t i = 0; i < dict_index_get_n_fields(index); i++) { + dict_field_t* field= dict_index_get_nth_field(index, i); + if (field->prefix_len || field->descending) { + wide_format = true; + break; + } + } + + for (ulint i = 0; i < dict_index_get_n_fields(index); i++) { + const dict_field_t& f = index->fields[i]; + DBUG_ASSERT(!f.name == f.col->is_dropped()); + + if (!f.name || my_strcasecmp(system_charset_info, + f.name, from)) { + continue; + } + + pars_info_t* info = pars_info_create(); + ulint pos = wide_format + ? i << 16 | f.prefix_len + | !!f.descending << 15 + : i; + pars_info_add_ull_literal(info, "indexid", index->id); + pars_info_add_int4_literal(info, "nth", pos); + pars_info_add_str_literal(info, "new", to); + + error = que_eval_sql( + info, + "PROCEDURE RENAME_SYS_FIELDS_PROC () IS\n" + "BEGIN\n" + "UPDATE SYS_FIELDS SET COL_NAME=:new\n" + "WHERE INDEX_ID=:indexid\n" + "AND POS=:nth;\n" + "END;\n", trx); + DBUG_EXECUTE_IF("ib_rename_column_error", + error = DB_OUT_OF_FILE_SPACE;); + + if (error != DB_SUCCESS) { + goto err_exit; + } + + if (!wide_format || !clust_has_wide_format + || f.prefix_len || f.descending) { + continue; + } + + /* For secondary indexes, the + wide_format check can be 'polluted' + by PRIMARY KEY column prefix or descending + field. Try also the simpler encoding + of SYS_FIELDS.POS. */ + info = pars_info_create(); + + pars_info_add_ull_literal(info, "indexid", index->id); + pars_info_add_int4_literal(info, "nth", i); + pars_info_add_str_literal(info, "new", to); + + error = que_eval_sql( + info, + "PROCEDURE RENAME_SYS_FIELDS_PROC () IS\n" + "BEGIN\n" + "UPDATE SYS_FIELDS SET COL_NAME=:new\n" + "WHERE INDEX_ID=:indexid\n" + "AND POS=:nth;\n" + "END;\n", trx); + + if (error != DB_SUCCESS) { + goto err_exit; + } + } + + if (index == dict_table_get_first_index(ctx.old_table)) { + clust_has_wide_format = wide_format; + } + } + + if (error != DB_SUCCESS) { +err_exit: + my_error_innodb(error, table_name, 0); + trx->error_state = DB_SUCCESS; + trx->op_info = ""; + DBUG_RETURN(true); + } + +rename_foreign: + trx->op_info = "renaming column in SYS_FOREIGN_COLS"; + + std::set<dict_foreign_t*> fk_evict; + bool foreign_modified; + + for (dict_foreign_set::const_iterator it = ctx.old_table->foreign_set.begin(); + it != ctx.old_table->foreign_set.end(); + ++it) { + + dict_foreign_t* foreign = *it; + foreign_modified = false; + + for (unsigned i = 0; i < foreign->n_fields; i++) { + if (my_strcasecmp(system_charset_info, + foreign->foreign_col_names[i], + from)) { + continue; + } + + /* Ignore the foreign key rename if fk info + is being dropped. */ + if (innobase_dropping_foreign( + foreign, ctx.drop_fk, + ctx.num_to_drop_fk)) { + continue; + } + + pars_info_t* info = pars_info_create(); + + pars_info_add_str_literal(info, "id", foreign->id); + pars_info_add_int4_literal(info, "nth", i); + pars_info_add_str_literal(info, "new", to); + + error = que_eval_sql( + info, + "PROCEDURE RENAME_SYS_FOREIGN_F_PROC () IS\n" + "BEGIN\n" + "UPDATE SYS_FOREIGN_COLS\n" + "SET FOR_COL_NAME=:new\n" + "WHERE ID=:id AND POS=:nth;\n" + "END;\n", trx); + + if (error != DB_SUCCESS) { + goto err_exit; + } + foreign_modified = true; + } + + if (foreign_modified) { + fk_evict.insert(foreign); + } + } + + for (dict_foreign_set::const_iterator it + = ctx.old_table->referenced_set.begin(); + it != ctx.old_table->referenced_set.end(); + ++it) { + + foreign_modified = false; + dict_foreign_t* foreign = *it; + + for (unsigned i = 0; i < foreign->n_fields; i++) { + if (my_strcasecmp(system_charset_info, + foreign->referenced_col_names[i], + from)) { + continue; + } + + pars_info_t* info = pars_info_create(); + + pars_info_add_str_literal(info, "id", foreign->id); + pars_info_add_int4_literal(info, "nth", i); + pars_info_add_str_literal(info, "new", to); + + error = que_eval_sql( + info, + "PROCEDURE RENAME_SYS_FOREIGN_R_PROC () IS\n" + "BEGIN\n" + "UPDATE SYS_FOREIGN_COLS\n" + "SET REF_COL_NAME=:new\n" + "WHERE ID=:id AND POS=:nth;\n" + "END;\n", trx); + + if (error != DB_SUCCESS) { + goto err_exit; + } + foreign_modified = true; + } + + if (foreign_modified) { + fk_evict.insert(foreign); + } + } + + /* Reload the foreign key info for instant table too. */ + if (ctx.need_rebuild() || ctx.is_instant()) { + std::for_each(fk_evict.begin(), fk_evict.end(), + dict_foreign_remove_from_cache); + } + + trx->op_info = ""; + DBUG_RETURN(false); +} + +/** Rename columns in the data dictionary tables. +@param ha_alter_info Data used during in-place alter. +@param ctx In-place ALTER TABLE context +@param table the TABLE +@param trx data dictionary transaction +@param table_name Table name in MySQL +@retval true Failure +@retval false Success */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_rename_columns_try( +/*========================*/ + Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx*ctx, + const TABLE* table, + trx_t* trx, + const char* table_name) +{ + uint i = 0; + + DBUG_ASSERT(ctx->need_rebuild()); + DBUG_ASSERT(ha_alter_info->handler_flags + & ALTER_COLUMN_NAME); + + for (Field** fp = table->field; *fp; fp++, i++) { + if (!((*fp)->flags & FIELD_IS_RENAMED)) { + continue; + } + + for (const Create_field& cf : + ha_alter_info->alter_info->create_list) { + if (cf.field == *fp) { + if (innobase_rename_column_try( + *ctx, trx, table_name, + cf.field->field_name.str, + cf.field_name.str)) { + return(true); + } + goto processed_field; + } + } + + ut_error; +processed_field: + continue; + } + + return(false); +} + +/** Enlarge a column in the data dictionary tables. +@param ctx In-place ALTER TABLE context +@param trx data dictionary transaction +@param table_name Table name in MySQL +@param pos 0-based index to user_table->cols[] or user_table->v_cols[] +@param f new column +@param is_v if it's a virtual column +@retval true Failure +@retval false Success */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_rename_or_enlarge_column_try( + ha_innobase_inplace_ctx*ctx, + trx_t* trx, + const char* table_name, + ulint pos, + const Field& f, + bool is_v) +{ + dict_col_t* col; + dict_table_t* user_table = ctx->old_table; + + DBUG_ENTER("innobase_rename_or_enlarge_column_try"); + DBUG_ASSERT(!ctx->need_rebuild()); + + DBUG_ASSERT(trx->dict_operation); + ut_ad(trx->dict_operation_lock_mode); + ut_ad(dict_sys.locked()); + + ulint n_base; + + if (is_v) { + dict_v_col_t* v_col= dict_table_get_nth_v_col(user_table, pos); + pos = dict_create_v_col_pos(v_col->v_pos, v_col->m_col.ind); + col = &v_col->m_col; + n_base = v_col->num_base; + } else { + col = dict_table_get_nth_col(user_table, pos); + n_base = 0; + } + + unsigned prtype; + uint8_t mtype; + uint16_t len; + get_type(f, prtype, mtype, len); + DBUG_ASSERT(!dtype_is_string_type(col->mtype) + || col->mbminlen == f.charset()->mbminlen); + DBUG_ASSERT(col->len <= len); + +#ifdef UNIV_DEBUG + ut_ad(col->mbminlen <= col->mbmaxlen); + switch (mtype) { + case DATA_MYSQL: + if (!(prtype & DATA_BINARY_TYPE) || user_table->not_redundant() + || col->mbminlen != col->mbmaxlen) { + /* NOTE: we could allow this when !(prtype & + DATA_BINARY_TYPE) and ROW_FORMAT is not REDUNDANT and + mbminlen<mbmaxlen. That is, we treat a UTF-8 CHAR(n) + column somewhat like a VARCHAR. */ + break; + } + /* fall through */ + case DATA_FIXBINARY: + case DATA_CHAR: + ut_ad(col->len == len); + break; + case DATA_BINARY: + case DATA_VARCHAR: + case DATA_VARMYSQL: + case DATA_DECIMAL: + case DATA_BLOB: + break; + default: + ut_ad(!((col->prtype ^ prtype) & ~DATA_VERSIONED)); + ut_ad(col->mtype == mtype); + ut_ad(col->len == len); + } +#endif /* UNIV_DEBUG */ + + const char* col_name = col->name(*user_table); + const bool same_name = !strcmp(col_name, f.field_name.str); + + if (!same_name + && innobase_rename_column_try(*ctx, trx, table_name, + col_name, f.field_name.str)) { + DBUG_RETURN(true); + } + + if (same_name + && col->prtype == prtype && col->mtype == mtype + && col->len == len) { + DBUG_RETURN(false); + } + + DBUG_RETURN(innodb_insert_sys_columns(user_table->id, pos, + f.field_name.str, + mtype, prtype, len, + n_base, trx, true)); +} + +/** Rename or enlarge columns in the data dictionary cache +as part of commit_try_norebuild(). +@param ha_alter_info Data used during in-place alter. +@param ctx In-place ALTER TABLE context +@param altered_table metadata after ALTER TABLE +@param table metadata before ALTER TABLE +@param trx data dictionary transaction +@param table_name Table name in MySQL +@retval true Failure +@retval false Success */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_rename_or_enlarge_columns_try( + Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx*ctx, + const TABLE* altered_table, + const TABLE* table, + trx_t* trx, + const char* table_name) +{ + DBUG_ENTER("innobase_rename_or_enlarge_columns_try"); + + if (!(ha_alter_info->handler_flags + & (ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE + | ALTER_COLUMN_NAME))) { + DBUG_RETURN(false); + } + + ulint i = 0; + ulint num_v = 0; + + for (Field** fp = table->field; *fp; fp++, i++) { + const bool is_v = !(*fp)->stored_in_db(); + ulint idx = is_v ? num_v++ : i - num_v; + + Field** af = altered_table->field; + for (const Create_field& cf : + ha_alter_info->alter_info->create_list) { + if (cf.field == *fp) { + if (innobase_rename_or_enlarge_column_try( + ctx, trx, table_name, + idx, **af, is_v)) { + DBUG_RETURN(true); + } + break; + } + af++; + } + } + + DBUG_RETURN(false); +} + +/** Rename or enlarge columns in the data dictionary cache +as part of commit_cache_norebuild(). +@param ha_alter_info Data used during in-place alter. +@param altered_table metadata after ALTER TABLE +@param table metadata before ALTER TABLE +@param user_table InnoDB table that was being altered */ +static MY_ATTRIBUTE((nonnull)) +void +innobase_rename_or_enlarge_columns_cache( +/*=====================================*/ + Alter_inplace_info* ha_alter_info, + const TABLE* altered_table, + const TABLE* table, + dict_table_t* user_table) +{ + if (!(ha_alter_info->handler_flags + & (ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE + | ALTER_COLUMN_NAME))) { + return; + } + + uint i = 0; + ulint num_v = 0; + + for (Field** fp = table->field; *fp; fp++, i++) { + const bool is_virtual = !(*fp)->stored_in_db(); + + Field** af = altered_table->field; + for (Create_field& cf : + ha_alter_info->alter_info->create_list) { + if (cf.field != *fp) { + af++; + continue; + } + + ulint col_n = is_virtual ? num_v : i - num_v; + dict_col_t *col = is_virtual + ? &dict_table_get_nth_v_col(user_table, col_n) + ->m_col + : dict_table_get_nth_col(user_table, col_n); + const bool is_string= dtype_is_string_type(col->mtype); + DBUG_ASSERT(col->mbminlen + == (is_string + ? (*af)->charset()->mbminlen : 0)); + unsigned prtype; + uint8_t mtype; + uint16_t len; + get_type(**af, prtype, mtype, len); + DBUG_ASSERT(is_string == dtype_is_string_type(mtype)); + + col->prtype = prtype; + col->mtype = mtype; + col->len = len; + col->mbmaxlen = is_string + ? (*af)->charset()->mbmaxlen & 7: 0; + + if ((*fp)->flags & FIELD_IS_RENAMED) { + dict_mem_table_col_rename( + user_table, col_n, + cf.field->field_name.str, + (*af)->field_name.str, is_virtual); + } + + break; + } + + if (is_virtual) { + num_v++; + } + } +} + +/** Set the auto-increment value of the table on commit. +@param ha_alter_info Data used during in-place alter +@param ctx In-place ALTER TABLE context +@param altered_table MySQL table that is being altered +@param old_table MySQL table as it is before the ALTER operation +@return whether the operation failed (and my_error() was called) */ +static MY_ATTRIBUTE((nonnull)) +bool +commit_set_autoinc( + Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx*ctx, + const TABLE* altered_table, + const TABLE* old_table) +{ + DBUG_ENTER("commit_set_autoinc"); + + if (!altered_table->found_next_number_field) { + /* There is no AUTO_INCREMENT column in the table + after the ALTER operation. */ + } else if (ctx->add_autoinc != ULINT_UNDEFINED) { + ut_ad(ctx->need_rebuild()); + /* An AUTO_INCREMENT column was added. Get the last + value from the sequence, which may be based on a + supplied AUTO_INCREMENT value. */ + ib_uint64_t autoinc = ctx->sequence.last(); + ctx->new_table->autoinc = autoinc; + /* Bulk index creation does not update + PAGE_ROOT_AUTO_INC, so we must persist the "last used" + value here. */ + btr_write_autoinc(dict_table_get_first_index(ctx->new_table), + autoinc - 1, true); + } else if ((ha_alter_info->handler_flags + & ALTER_CHANGE_CREATE_OPTION) + && (ha_alter_info->create_info->used_fields + & HA_CREATE_USED_AUTO)) { + + if (!ctx->old_table->space) { + my_error(ER_TABLESPACE_DISCARDED, MYF(0), + old_table->s->table_name.str); + DBUG_RETURN(true); + } + + /* An AUTO_INCREMENT value was supplied by the user. + It must be persisted to the data file. */ + const Field* ai = old_table->found_next_number_field; + ut_ad(!strcmp(dict_table_get_col_name(ctx->old_table, + innodb_col_no(ai)), + ai->field_name.str)); + + ib_uint64_t autoinc + = ha_alter_info->create_info->auto_increment_value; + if (autoinc == 0) { + autoinc = 1; + } + + if (autoinc >= ctx->old_table->autoinc) { + /* Persist the predecessor of the + AUTO_INCREMENT value as the last used one. */ + ctx->new_table->autoinc = autoinc--; + } else { + /* Mimic ALGORITHM=COPY in the following scenario: + + CREATE TABLE t (a SERIAL); + INSERT INTO t SET a=100; + ALTER TABLE t AUTO_INCREMENT = 1; + INSERT INTO t SET a=NULL; + SELECT * FROM t; + + By default, ALGORITHM=INPLACE would reset the + sequence to 1, while after ALGORITHM=COPY, the + last INSERT would use a value larger than 100. + + We could only search the tree to know current + max counter in the table and compare. */ + const dict_col_t* autoinc_col + = dict_table_get_nth_col(ctx->old_table, + innodb_col_no(ai)); + dict_index_t* index + = dict_table_get_first_index(ctx->old_table); + while (index != NULL + && index->fields[0].col != autoinc_col) { + index = dict_table_get_next_index(index); + } + + ut_ad(index); + + ib_uint64_t max_in_table = index + ? row_search_max_autoinc(index) + : 0; + + if (autoinc <= max_in_table) { + ctx->new_table->autoinc = innobase_next_autoinc( + max_in_table, 1, + ctx->prebuilt->autoinc_increment, + ctx->prebuilt->autoinc_offset, + innobase_get_int_col_max_value(ai)); + /* Persist the maximum value as the + last used one. */ + autoinc = max_in_table; + } else { + /* Persist the predecessor of the + AUTO_INCREMENT value as the last used one. */ + ctx->new_table->autoinc = autoinc--; + } + } + + btr_write_autoinc(dict_table_get_first_index(ctx->new_table), + autoinc, true); + } else if (ctx->need_rebuild()) { + /* No AUTO_INCREMENT value was specified. + Copy it from the old table. */ + ctx->new_table->autoinc = ctx->old_table->autoinc; + /* The persistent value was already copied in + prepare_inplace_alter_table_dict() when ctx->new_table + was created. If this was a LOCK=NONE operation, the + AUTO_INCREMENT values would be updated during + row_log_table_apply(). If this was LOCK!=NONE, + the table contents could not possibly have changed + between prepare_inplace and commit_inplace. */ + } + + DBUG_RETURN(false); +} + +/** Add or drop foreign key constraints to the data dictionary tables, +but do not touch the data dictionary cache. +@param ha_alter_info Data used during in-place alter +@param ctx In-place ALTER TABLE context +@param trx Data dictionary transaction +@param table_name Table name in MySQL +@retval true Failure +@retval false Success +*/ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +innobase_update_foreign_try( +/*========================*/ + ha_innobase_inplace_ctx*ctx, + trx_t* trx, + const char* table_name) +{ + ulint foreign_id; + ulint i; + + DBUG_ENTER("innobase_update_foreign_try"); + + foreign_id = dict_table_get_highest_foreign_id(ctx->new_table); + + foreign_id++; + + for (i = 0; i < ctx->num_to_add_fk; i++) { + dict_foreign_t* fk = ctx->add_fk[i]; + + ut_ad(fk->foreign_table == ctx->new_table + || fk->foreign_table == ctx->old_table); + + dberr_t error = dict_create_add_foreign_id( + &foreign_id, ctx->old_table->name.m_name, fk); + + if (error != DB_SUCCESS) { + my_error(ER_TOO_LONG_IDENT, MYF(0), + fk->id); + DBUG_RETURN(true); + } + + if (!fk->foreign_index) { + fk->foreign_index = dict_foreign_find_index( + ctx->new_table, ctx->col_names, + fk->foreign_col_names, + fk->n_fields, fk->referenced_index, TRUE, + fk->type + & (DICT_FOREIGN_ON_DELETE_SET_NULL + | DICT_FOREIGN_ON_UPDATE_SET_NULL), + NULL, NULL, NULL); + if (!fk->foreign_index) { + my_error(ER_FK_INCORRECT_OPTION, + MYF(0), table_name, fk->id); + DBUG_RETURN(true); + } + } + + /* The fk->foreign_col_names[] uses renamed column + names, while the columns in ctx->old_table have not + been renamed yet. */ + error = dict_create_add_foreign_to_dictionary( + ctx->old_table->name.m_name, fk, trx); + + DBUG_EXECUTE_IF( + "innodb_test_cannot_add_fk_system", + error = DB_ERROR;); + + if (error != DB_SUCCESS) { + my_error(ER_FK_FAIL_ADD_SYSTEM, MYF(0), + fk->id); + DBUG_RETURN(true); + } + } + + for (i = 0; i < ctx->num_to_drop_fk; i++) { + dict_foreign_t* fk = ctx->drop_fk[i]; + + DBUG_ASSERT(fk->foreign_table == ctx->old_table); + + if (innobase_drop_foreign_try(trx, table_name, fk->id)) { + DBUG_RETURN(true); + } + } + + DBUG_RETURN(false); +} + +/** Update the foreign key constraint definitions in the data dictionary cache +after the changes to data dictionary tables were committed. +@param ctx In-place ALTER TABLE context +@param user_thd MySQL connection +@return InnoDB error code (should always be DB_SUCCESS) */ +static MY_ATTRIBUTE((nonnull, warn_unused_result)) +dberr_t +innobase_update_foreign_cache( +/*==========================*/ + ha_innobase_inplace_ctx* ctx, + THD* user_thd) +{ + dict_table_t* user_table; + dberr_t err = DB_SUCCESS; + + DBUG_ENTER("innobase_update_foreign_cache"); + + ut_ad(dict_sys.locked()); + + user_table = ctx->old_table; + + /* Discard the added foreign keys, because we will + load them from the data dictionary. */ + for (ulint i = 0; i < ctx->num_to_add_fk; i++) { + dict_foreign_t* fk = ctx->add_fk[i]; + dict_foreign_free(fk); + } + + if (ctx->need_rebuild()) { + /* The rebuilt table is already using the renamed + column names. No need to pass col_names or to drop + constraints from the data dictionary cache. */ + DBUG_ASSERT(!ctx->col_names); + user_table = ctx->new_table; + } else { + /* Drop the foreign key constraints if the + table was not rebuilt. If the table is rebuilt, + there would not be any foreign key contraints for + it yet in the data dictionary cache. */ + for (ulint i = 0; i < ctx->num_to_drop_fk; i++) { + dict_foreign_t* fk = ctx->drop_fk[i]; + dict_foreign_remove_from_cache(fk); + } + } + + /* Load the old or added foreign keys from the data dictionary + and prevent the table from being evicted from the data + dictionary cache (work around the lack of WL#6049). */ + dict_names_t fk_tables; + + err = dict_load_foreigns(user_table->name.m_name, + ctx->col_names, 1, true, + DICT_ERR_IGNORE_FK_NOKEY, + fk_tables); + + if (err == DB_CANNOT_ADD_CONSTRAINT) { + fk_tables.clear(); + + /* It is possible there are existing foreign key are + loaded with "foreign_key checks" off, + so let's retry the loading with charset_check is off */ + err = dict_load_foreigns(user_table->name.m_name, + ctx->col_names, 1, false, + DICT_ERR_IGNORE_NONE, + fk_tables); + + /* The load with "charset_check" off is successful, warn + the user that the foreign key has loaded with mis-matched + charset */ + if (err == DB_SUCCESS) { + push_warning_printf( + user_thd, + Sql_condition::WARN_LEVEL_WARN, + ER_ALTER_INFO, + "Foreign key constraints for table '%s'" + " are loaded with charset check off", + user_table->name.m_name); + } + } + + /* For complete loading of foreign keys, all associated tables must + also be loaded. */ + while (err == DB_SUCCESS && !fk_tables.empty()) { + const char *f = fk_tables.front(); + if (!dict_sys.load_table({f, strlen(f)})) { + err = DB_TABLE_NOT_FOUND; + ib::error() + << "Failed to load table " + << table_name_t(const_cast<char*>(f)) + << " which has a foreign key constraint with" + << user_table->name; + break; + } + + fk_tables.pop_front(); + } + + DBUG_RETURN(err); +} + +/** Changes SYS_COLUMNS.PRTYPE for one column. +@param[in,out] trx transaction +@param[in] table_name table name +@param[in] tableid table ID as in SYS_TABLES +@param[in] pos column position +@param[in] prtype new precise type +@return boolean flag +@retval true on failure +@retval false on success */ +static +bool +vers_change_field_try( + trx_t* trx, + const char* table_name, + const table_id_t tableid, + const ulint pos, + const ulint prtype) +{ + DBUG_ENTER("vers_change_field_try"); + + pars_info_t* info = pars_info_create(); + + pars_info_add_int4_literal(info, "prtype", prtype); + pars_info_add_ull_literal(info,"tableid", tableid); + pars_info_add_int4_literal(info, "pos", pos); + + dberr_t error = que_eval_sql(info, + "PROCEDURE CHANGE_COLUMN_MTYPE () IS\n" + "BEGIN\n" + "UPDATE SYS_COLUMNS SET PRTYPE=:prtype\n" + "WHERE TABLE_ID=:tableid AND POS=:pos;\n" + "END;\n", trx); + + if (error != DB_SUCCESS) { + my_error_innodb(error, table_name, 0); + trx->error_state = DB_SUCCESS; + trx->op_info = ""; + DBUG_RETURN(true); + } + + DBUG_RETURN(false); +} + +/** Changes fields WITH/WITHOUT SYSTEM VERSIONING property in SYS_COLUMNS. +@param[in] ha_alter_info alter info +@param[in] ctx alter inplace context +@param[in] trx transaction +@param[in] table old table +@return boolean flag +@retval true on failure +@retval false on success */ +static +bool +vers_change_fields_try( + const Alter_inplace_info* ha_alter_info, + const ha_innobase_inplace_ctx* ctx, + trx_t* trx, + const TABLE* table) +{ + DBUG_ENTER("vers_change_fields_try"); + + DBUG_ASSERT(ha_alter_info); + DBUG_ASSERT(ctx); + + for (const Create_field& create_field : ha_alter_info->alter_info->create_list) { + if (!create_field.field) { + continue; + } + if (create_field.versioning + == Column_definition::VERSIONING_NOT_SET) { + continue; + } + + const dict_table_t* new_table = ctx->new_table; + const uint pos = innodb_col_no(create_field.field); + const dict_col_t* col = dict_table_get_nth_col(new_table, pos); + + DBUG_ASSERT(!col->vers_sys_start()); + DBUG_ASSERT(!col->vers_sys_end()); + + ulint new_prtype + = create_field.versioning + == Column_definition::WITHOUT_VERSIONING + ? col->prtype & ~DATA_VERSIONED + : col->prtype | DATA_VERSIONED; + + if (vers_change_field_try(trx, table->s->table_name.str, + new_table->id, pos, + new_prtype)) { + DBUG_RETURN(true); + } + } + + DBUG_RETURN(false); +} + +/** Changes WITH/WITHOUT SYSTEM VERSIONING for fields +in the data dictionary cache. +@param ha_alter_info Data used during in-place alter +@param ctx In-place ALTER TABLE context +@param table MySQL table as it is before the ALTER operation */ +static +void +vers_change_fields_cache( + Alter_inplace_info* ha_alter_info, + const ha_innobase_inplace_ctx* ctx, + const TABLE* table) +{ + DBUG_ENTER("vers_change_fields_cache"); + + DBUG_ASSERT(ha_alter_info); + DBUG_ASSERT(ctx); + DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_COLUMN_UNVERSIONED); + + for (const Create_field& create_field : + ha_alter_info->alter_info->create_list) { + if (!create_field.field || create_field.field->vcol_info) { + continue; + } + dict_col_t* col = dict_table_get_nth_col( + ctx->new_table, innodb_col_no(create_field.field)); + + if (create_field.versioning + == Column_definition::WITHOUT_VERSIONING) { + + DBUG_ASSERT(!col->vers_sys_start()); + DBUG_ASSERT(!col->vers_sys_end()); + col->prtype &= ~DATA_VERSIONED; + } else if (create_field.versioning + == Column_definition::WITH_VERSIONING) { + + DBUG_ASSERT(!col->vers_sys_start()); + DBUG_ASSERT(!col->vers_sys_end()); + col->prtype |= DATA_VERSIONED; + } + } + + DBUG_VOID_RETURN; +} + +/** Commit the changes made during prepare_inplace_alter_table() +and inplace_alter_table() inside the data dictionary tables, +when rebuilding the table. +@param ha_alter_info Data used during in-place alter +@param ctx In-place ALTER TABLE context +@param altered_table MySQL table that is being altered +@param old_table MySQL table as it is before the ALTER operation +@param trx Data dictionary transaction +@param table_name Table name in MySQL +@retval true Failure +@retval false Success +*/ +inline MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +commit_try_rebuild( +/*===============*/ + Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx*ctx, + TABLE* altered_table, + const TABLE* old_table, + bool statistics_exist, + trx_t* trx, + const char* table_name) +{ + dict_table_t* rebuilt_table = ctx->new_table; + dict_table_t* user_table = ctx->old_table; + + DBUG_ENTER("commit_try_rebuild"); + DBUG_ASSERT(ctx->need_rebuild()); + DBUG_ASSERT(trx->dict_operation_lock_mode); + DBUG_ASSERT(!(ha_alter_info->handler_flags + & ALTER_DROP_FOREIGN_KEY) + || ctx->num_to_drop_fk > 0); + DBUG_ASSERT(ctx->num_to_drop_fk + <= ha_alter_info->alter_info->drop_list.elements); + + innobase_online_rebuild_log_free(user_table); + + for (dict_index_t* index = dict_table_get_first_index(rebuilt_table); + index; + index = dict_table_get_next_index(index)) { + DBUG_ASSERT(dict_index_get_online_status(index) + == ONLINE_INDEX_COMPLETE); + DBUG_ASSERT(index->is_committed()); + if (index->is_corrupted()) { + my_error(ER_INDEX_CORRUPT, MYF(0), index->name()); + DBUG_RETURN(true); + } + } + + if (innobase_update_foreign_try(ctx, trx, table_name)) { + DBUG_RETURN(true); + } + + /* Clear the to_be_dropped flag in the data dictionary cache + of user_table. */ + for (ulint i = 0; i < ctx->num_to_drop_index; i++) { + dict_index_t* index = ctx->drop_index[i]; + DBUG_ASSERT(index->table == user_table); + DBUG_ASSERT(index->is_committed()); + DBUG_ASSERT(index->to_be_dropped); + index->to_be_dropped = 0; + } + + if ((ha_alter_info->handler_flags + & ALTER_COLUMN_NAME) + && innobase_rename_columns_try(ha_alter_info, ctx, old_table, + trx, table_name)) { + DBUG_RETURN(true); + } + + /* The new table must inherit the flag from the + "parent" table. */ + if (!user_table->space) { + rebuilt_table->file_unreadable = true; + rebuilt_table->flags2 |= DICT_TF2_DISCARDED; + } + + /* We can now rename the old table as a temporary table, + rename the new temporary table as the old table and drop the + old table. */ + char* old_name= mem_heap_strdup(ctx->heap, user_table->name.m_name); + + dberr_t error = row_rename_table_for_mysql(user_table->name.m_name, + ctx->tmp_name, trx, false); + if (error == DB_SUCCESS) { + error = row_rename_table_for_mysql( + rebuilt_table->name.m_name, old_name, trx, false); + if (error == DB_SUCCESS) { + /* The statistics for the surviving indexes will be + re-inserted in alter_stats_rebuild(). */ + if (statistics_exist) { + error = trx->drop_table_statistics(old_name); + } + if (error == DB_SUCCESS) { + error = trx->drop_table(*user_table); + } + } + } + + /* We must be still holding a table handle. */ + DBUG_ASSERT(user_table->get_ref_count() == 1); + DBUG_EXECUTE_IF("ib_rebuild_cannot_rename", error = DB_ERROR;); + + switch (error) { + case DB_SUCCESS: + DBUG_RETURN(false); + case DB_TABLESPACE_EXISTS: + ut_a(rebuilt_table->get_ref_count() == 1); + my_error(ER_TABLESPACE_EXISTS, MYF(0), ctx->tmp_name); + DBUG_RETURN(true); + case DB_DUPLICATE_KEY: + ut_a(rebuilt_table->get_ref_count() == 1); + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), ctx->tmp_name); + DBUG_RETURN(true); + default: + my_error_innodb(error, table_name, user_table->flags); + DBUG_RETURN(true); + } +} + +/** Rename indexes in dictionary. +@param[in] ctx alter info context +@param[in] ha_alter_info Operation used during inplace alter +@param[out] trx transaction to change the index name + in dictionary +@return true if it failed to rename +@return false if it is success. */ +static +bool +rename_indexes_try( + const ha_innobase_inplace_ctx* ctx, + const Alter_inplace_info* ha_alter_info, + trx_t* trx) +{ + DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_RENAME_INDEX); + + for (const Alter_inplace_info::Rename_key_pair& pair : + ha_alter_info->rename_keys) { + dict_index_t* index = dict_table_get_index_on_name( + ctx->old_table, pair.old_key->name.str); + // This was checked previously in + // ha_innobase::prepare_inplace_alter_table() + ut_ad(index); + + if (rename_index_try(index, pair.new_key->name.str, trx)) { + return true; + } + } + + return false; +} + +/** Set of column numbers */ +typedef std::set<ulint, std::less<ulint>, ut_allocator<ulint> > col_set; + +/** Collect (not instantly dropped) columns from dropped indexes +@param[in] ctx In-place ALTER TABLE context +@param[in, out] drop_col_list list which will be set, containing columns + which is part of index being dropped +@param[in, out] drop_v_col_list list which will be set, containing + virtual columns which is part of index + being dropped */ +static +void +collect_columns_from_dropped_indexes( + const ha_innobase_inplace_ctx* ctx, + col_set& drop_col_list, + col_set& drop_v_col_list) +{ + for (ulint index_count = 0; index_count < ctx->num_to_drop_index; + index_count++) { + const dict_index_t* index = ctx->drop_index[index_count]; + + for (ulint col = 0; col < index->n_user_defined_cols; col++) { + const dict_col_t* idx_col + = dict_index_get_nth_col(index, col); + + if (idx_col->is_virtual()) { + const dict_v_col_t* v_col + = reinterpret_cast< + const dict_v_col_t*>(idx_col); + drop_v_col_list.insert(v_col->v_pos); + + } else { + ulint col_no = dict_col_get_no(idx_col); + if (ctx->col_map + && ctx->col_map[col_no] + == ULINT_UNDEFINED) { + // this column was instantly dropped + continue; + } + drop_col_list.insert(col_no); + } + } + } +} + +/** Change PAGE_COMPRESSED to ON or change the PAGE_COMPRESSION_LEVEL. +@param[in] level PAGE_COMPRESSION_LEVEL +@param[in] table table before the change +@param[in,out] trx data dictionary transaction +@param[in] table_name table name in MariaDB +@return whether the operation succeeded */ +MY_ATTRIBUTE((nonnull, warn_unused_result)) +static +bool +innobase_page_compression_try( + uint level, + const dict_table_t* table, + trx_t* trx, + const char* table_name) +{ + DBUG_ENTER("innobase_page_compression_try"); + DBUG_ASSERT(level >= 1); + DBUG_ASSERT(level <= 9); + + unsigned flags = table->flags + & ~(0xFU << DICT_TF_POS_PAGE_COMPRESSION_LEVEL); + flags |= 1U << DICT_TF_POS_PAGE_COMPRESSION + | level << DICT_TF_POS_PAGE_COMPRESSION_LEVEL; + + if (table->flags == flags) { + DBUG_RETURN(false); + } + + pars_info_t* info = pars_info_create(); + + pars_info_add_ull_literal(info, "id", table->id); + pars_info_add_int4_literal(info, "type", + dict_tf_to_sys_tables_type(flags)); + + dberr_t error = que_eval_sql(info, + "PROCEDURE CHANGE_COMPRESSION () IS\n" + "BEGIN\n" + "UPDATE SYS_TABLES SET TYPE=:type\n" + "WHERE ID=:id;\n" + "END;\n", trx); + + if (error != DB_SUCCESS) { + my_error_innodb(error, table_name, 0); + trx->error_state = DB_SUCCESS; + trx->op_info = ""; + DBUG_RETURN(true); + } + + DBUG_RETURN(false); +} + +/** Evict the table from cache and reopen it. Drop outdated statistics. +@param thd mariadb THD entity +@param table innodb table +@param table_name user-friendly table name for errors +@param ctx ALTER TABLE context +@return newly opened table */ +static dict_table_t *innobase_reload_table(THD *thd, dict_table_t *table, + const LEX_CSTRING &table_name, + ha_innobase_inplace_ctx &ctx) +{ + if (ctx.is_instant()) + { + for (auto i= ctx.old_n_v_cols; i--; ) + { + ctx.old_v_cols[i].~dict_v_col_t(); + const_cast<unsigned&>(ctx.old_n_v_cols)= 0; + } + } + + const table_id_t id= table->id; + table->release(); + dict_sys.remove(table); + return dict_table_open_on_id(id, true, DICT_TABLE_OP_NORMAL); +} + +/** Commit the changes made during prepare_inplace_alter_table() +and inplace_alter_table() inside the data dictionary tables, +when not rebuilding the table. +@param ha_alter_info Data used during in-place alter +@param ctx In-place ALTER TABLE context +@param old_table MySQL table as it is before the ALTER operation +@param trx Data dictionary transaction +@param table_name Table name in MySQL +@retval true Failure +@retval false Success +*/ +inline MY_ATTRIBUTE((nonnull, warn_unused_result)) +bool +commit_try_norebuild( +/*=================*/ + Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx*ctx, + TABLE* altered_table, + const TABLE* old_table, + trx_t* trx, + const char* table_name) +{ + DBUG_ENTER("commit_try_norebuild"); + DBUG_ASSERT(!ctx->need_rebuild()); + DBUG_ASSERT(trx->dict_operation_lock_mode); + DBUG_ASSERT(!(ha_alter_info->handler_flags + & ALTER_DROP_FOREIGN_KEY) + || ctx->num_to_drop_fk > 0); + DBUG_ASSERT(ctx->num_to_drop_fk + <= ha_alter_info->alter_info->drop_list.elements + || ctx->num_to_drop_vcol + == ha_alter_info->alter_info->drop_list.elements); + + if (ctx->page_compression_level + && innobase_page_compression_try(ctx->page_compression_level, + ctx->new_table, trx, + table_name)) { + DBUG_RETURN(true); + } + + for (ulint i = 0; i < ctx->num_to_add_index; i++) { + dict_index_t* index = ctx->add_index[i]; + DBUG_ASSERT(dict_index_get_online_status(index) + == ONLINE_INDEX_COMPLETE); + DBUG_ASSERT(!index->is_committed()); + if (index->is_corrupted()) { + /* Report a duplicate key + error for the index that was + flagged corrupted, most likely + because a duplicate value was + inserted (directly or by + rollback) after + ha_innobase::inplace_alter_table() + completed. + TODO: report this as a corruption + with a detailed reason once + WL#6379 has been implemented. */ + my_error(ER_DUP_UNKNOWN_IN_INDEX, + MYF(0), index->name()); + DBUG_RETURN(true); + } + } + + if (innobase_update_foreign_try(ctx, trx, table_name)) { + DBUG_RETURN(true); + } + + if ((ha_alter_info->handler_flags & ALTER_COLUMN_UNVERSIONED) + && vers_change_fields_try(ha_alter_info, ctx, trx, old_table)) { + DBUG_RETURN(true); + } + + dberr_t error = DB_SUCCESS; + dict_index_t* index; + const char *op = "rename index to add"; + ulint num_fts_index = 0; + + /* We altered the table in place. Mark the indexes as committed. */ + for (ulint i = 0; i < ctx->num_to_add_index; i++) { + index = ctx->add_index[i]; + DBUG_ASSERT(dict_index_get_online_status(index) + == ONLINE_INDEX_COMPLETE); + DBUG_ASSERT(!index->is_committed()); + error = row_merge_rename_index_to_add( + trx, ctx->new_table->id, index->id); + if (error) { + goto handle_error; + } + } + + for (dict_index_t *index = UT_LIST_GET_FIRST(ctx->old_table->indexes); + index; index = UT_LIST_GET_NEXT(indexes, index)) { + if (index->type & DICT_FTS) { + num_fts_index++; + } + } + + char db[MAX_DB_UTF8_LEN], table[MAX_TABLE_UTF8_LEN]; + if (ctx->num_to_drop_index) { + dict_fs2utf8(ctx->old_table->name.m_name, + db, sizeof db, table, sizeof table); + } + + for (ulint i = 0; i < ctx->num_to_drop_index; i++) { + index = ctx->drop_index[i]; + DBUG_ASSERT(index->is_committed()); + DBUG_ASSERT(index->table == ctx->new_table); + DBUG_ASSERT(index->to_be_dropped); + op = "DROP INDEX"; + + static const char drop_index[] = + "PROCEDURE DROP_INDEX_PROC () IS\n" + "BEGIN\n" + "DELETE FROM SYS_FIELDS WHERE INDEX_ID=:indexid;\n" + "DELETE FROM SYS_INDEXES WHERE ID=:indexid;\n" + "END;\n"; + + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "indexid", index->id); + error = que_eval_sql(info, drop_index, trx); + + if (error == DB_SUCCESS && index->type & DICT_FTS) { + DBUG_ASSERT(index->table->fts); + DEBUG_SYNC_C("norebuild_fts_drop"); + error = fts_drop_index(index->table, index, trx); + ut_ad(num_fts_index); + num_fts_index--; + } + + if (error != DB_SUCCESS) { + goto handle_error; + } + + error = dict_stats_delete_from_index_stats(db, table, + index->name, trx); + switch (error) { + case DB_SUCCESS: + case DB_STATS_DO_NOT_EXIST: + continue; + default: + goto handle_error; + } + } + + if (const size_t size = ha_alter_info->rename_keys.size()) { + char tmp_name[5]; + char db[MAX_DB_UTF8_LEN], table[MAX_TABLE_UTF8_LEN]; + + dict_fs2utf8(ctx->new_table->name.m_name, db, sizeof db, + table, sizeof table); + tmp_name[0]= (char)0xff; + for (size_t i = 0; error == DB_SUCCESS && i < size; i++) { + snprintf(tmp_name+1, sizeof(tmp_name)-1, "%zu", i); + error = dict_stats_rename_index(db, table, + ha_alter_info-> + rename_keys[i]. + old_key->name.str, + tmp_name, trx); + } + for (size_t i = 0; error == DB_SUCCESS && i < size; i++) { + snprintf(tmp_name+1, sizeof(tmp_name)-1, "%zu", i); + error = dict_stats_rename_index(db, table, tmp_name, + ha_alter_info + ->rename_keys[i]. + new_key->name.str, + trx); + } + + switch (error) { + case DB_SUCCESS: + case DB_STATS_DO_NOT_EXIST: + break; + case DB_DUPLICATE_KEY: + my_error(ER_DUP_KEY, MYF(0), + "mysql.innodb_index_stats"); + DBUG_RETURN(true); + default: + goto handle_error; + } + } + + if ((ctx->old_table->flags2 & DICT_TF2_FTS) && !num_fts_index) { + error = fts_drop_tables(trx, *ctx->old_table); + if (error != DB_SUCCESS) { +handle_error: + switch (error) { + case DB_TOO_MANY_CONCURRENT_TRXS: + my_error(ER_TOO_MANY_CONCURRENT_TRXS, MYF(0)); + break; + case DB_LOCK_WAIT_TIMEOUT: + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + break; + default: + sql_print_error("InnoDB: %s: %s\n", op, + ut_strerr(error)); + DBUG_ASSERT(error == DB_IO_ERROR + || error == DB_LOCK_TABLE_FULL + || error == DB_DECRYPTION_FAILED + || error == DB_PAGE_CORRUPTED + || error == DB_CORRUPTION); + my_error(ER_INTERNAL_ERROR, MYF(0), op); + } + + DBUG_RETURN(true); + } + } + + if (innobase_rename_or_enlarge_columns_try(ha_alter_info, ctx, + altered_table, old_table, + trx, table_name)) { + DBUG_RETURN(true); + } + + if ((ha_alter_info->handler_flags & ALTER_RENAME_INDEX) + && rename_indexes_try(ctx, ha_alter_info, trx)) { + DBUG_RETURN(true); + } + + if (ctx->is_instant()) { + DBUG_RETURN(innobase_instant_try(ha_alter_info, ctx, + altered_table, old_table, + trx)); + } + + if (ha_alter_info->handler_flags + & (ALTER_DROP_VIRTUAL_COLUMN | ALTER_ADD_VIRTUAL_COLUMN)) { + if ((ha_alter_info->handler_flags & ALTER_DROP_VIRTUAL_COLUMN) + && innobase_drop_virtual_try(ha_alter_info, ctx->old_table, + trx)) { + DBUG_RETURN(true); + } + + if ((ha_alter_info->handler_flags & ALTER_ADD_VIRTUAL_COLUMN) + && innobase_add_virtual_try(ha_alter_info, ctx->old_table, + trx)) { + DBUG_RETURN(true); + } + + unsigned n_col = ctx->old_table->n_cols + - DATA_N_SYS_COLS; + unsigned n_v_col = ctx->old_table->n_v_cols + + ctx->num_to_add_vcol - ctx->num_to_drop_vcol; + + if (innodb_update_cols( + ctx->old_table, + dict_table_encode_n_col(n_col, n_v_col) + | unsigned(ctx->old_table->flags & DICT_TF_COMPACT) + << 31, trx)) { + DBUG_RETURN(true); + } + } + + DBUG_RETURN(false); +} + +/** Commit the changes to the data dictionary cache +after a successful commit_try_norebuild() call. +@param ha_alter_info algorithm=inplace context +@param ctx In-place ALTER TABLE context for the current partition +@param altered_table the TABLE after the ALTER +@param table the TABLE before the ALTER +@param trx Data dictionary transaction +(will be started and committed, for DROP INDEX) +@return whether all replacements were found for dropped indexes */ +inline MY_ATTRIBUTE((nonnull)) +bool +commit_cache_norebuild( +/*===================*/ + Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx*ctx, + const TABLE* altered_table, + const TABLE* table, + trx_t* trx) +{ + DBUG_ENTER("commit_cache_norebuild"); + DBUG_ASSERT(!ctx->need_rebuild()); + DBUG_ASSERT(ctx->new_table->space != fil_system.temp_space); + DBUG_ASSERT(!ctx->new_table->is_temporary()); + + bool found = true; + + if (ctx->page_compression_level) { + DBUG_ASSERT(ctx->new_table->space != fil_system.sys_space); +#if defined __GNUC__ && !defined __clang__ && __GNUC__ < 6 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" /* GCC 4 and 5 need this here */ +#endif + ctx->new_table->flags + = static_cast<uint16_t>( + (ctx->new_table->flags + & ~(0xFU + << DICT_TF_POS_PAGE_COMPRESSION_LEVEL)) + | 1 << DICT_TF_POS_PAGE_COMPRESSION + | (ctx->page_compression_level & 0xF) + << DICT_TF_POS_PAGE_COMPRESSION_LEVEL) + & ((1U << DICT_TF_BITS) - 1); +#if defined __GNUC__ && !defined __clang__ && __GNUC__ < 6 +# pragma GCC diagnostic pop +#endif + + if (fil_space_t* space = ctx->new_table->space) { + bool update = !(space->flags + & FSP_FLAGS_MASK_PAGE_COMPRESSION); + mysql_mutex_lock(&fil_system.mutex); + space->flags &= ~FSP_FLAGS_MASK_MEM_COMPRESSION_LEVEL; + space->flags |= ctx->page_compression_level + << FSP_FLAGS_MEM_COMPRESSION_LEVEL; + if (!space->full_crc32()) { + space->flags + |= FSP_FLAGS_MASK_PAGE_COMPRESSION; + } else if (!space->is_compressed()) { + space->flags |= static_cast<uint32_t>( + innodb_compression_algorithm) + << FSP_FLAGS_FCRC32_POS_COMPRESSED_ALGO; + } + mysql_mutex_unlock(&fil_system.mutex); + + if (update) { + /* Maybe we should introduce an undo + log record for updating tablespace + flags, and perform the update already + in innobase_page_compression_try(). + + If the server is killed before the + following mini-transaction commit + becomes durable, fsp_flags_try_adjust() + will perform the equivalent adjustment + and warn "adjusting FSP_SPACE_FLAGS". */ + mtr_t mtr; + mtr.start(); + if (buf_block_t* b = buf_page_get( + page_id_t(space->id, 0), + space->zip_size(), + RW_X_LATCH, &mtr)) { + byte* f = FSP_HEADER_OFFSET + + FSP_SPACE_FLAGS + + b->page.frame; + const auto sf = space->flags + & ~FSP_FLAGS_MEM_MASK; + if (mach_read_from_4(f) != sf) { + mtr.set_named_space(space); + mtr.write<4,mtr_t::FORCED>( + *b, f, sf); + } + } + mtr.commit(); + } + } + } + + col_set drop_list; + col_set v_drop_list; + + /* Check if the column, part of an index to be dropped is part of any + other index which is not being dropped. If it so, then set the ord_part + of the column to 0. */ + collect_columns_from_dropped_indexes(ctx, drop_list, v_drop_list); + + for (ulint col : drop_list) { + if (!check_col_exists_in_indexes(ctx->new_table, col, false)) { + ctx->new_table->cols[col].ord_part = 0; + } + } + + for (ulint col : v_drop_list) { + if (!check_col_exists_in_indexes(ctx->new_table, col, true)) { + ctx->new_table->v_cols[col].m_col.ord_part = 0; + } + } + + for (ulint i = 0; i < ctx->num_to_add_index; i++) { + dict_index_t* index = ctx->add_index[i]; + DBUG_ASSERT(dict_index_get_online_status(index) + == ONLINE_INDEX_COMPLETE); + DBUG_ASSERT(!index->is_committed()); + index->change_col_info = nullptr; + index->set_committed(true); + } + + for (ulint i = 0; i < ctx->num_to_drop_index; i++) { + dict_index_t* index = ctx->drop_index[i]; + DBUG_ASSERT(index->is_committed()); + DBUG_ASSERT(index->table == ctx->new_table); + DBUG_ASSERT(index->to_be_dropped); + + if (!dict_foreign_replace_index(index->table, ctx->col_names, + index)) { + found = false; + } + + dict_index_remove_from_cache(index->table, index); + } + + fts_clear_all(ctx->old_table); + + if (!ctx->is_instant()) { + innobase_rename_or_enlarge_columns_cache( + ha_alter_info, altered_table, table, ctx->new_table); + } else { + ut_ad(ctx->col_map); + + if (fts_t* fts = ctx->new_table->fts) { + ut_ad(fts->doc_col != ULINT_UNDEFINED); + ut_ad(ctx->new_table->n_cols > DATA_N_SYS_COLS); + const ulint c = ctx->col_map[fts->doc_col]; + ut_ad(c < ulint(ctx->new_table->n_cols) + - DATA_N_SYS_COLS); + ut_d(const dict_col_t& col = ctx->new_table->cols[c]); + ut_ad(!col.is_nullable()); + ut_ad(!col.is_virtual()); + ut_ad(!col.is_added()); + ut_ad(col.prtype & DATA_UNSIGNED); + ut_ad(col.mtype == DATA_INT); + ut_ad(col.len == 8); + ut_ad(col.ord_part); + fts->doc_col = c; + } + + if (ha_alter_info->handler_flags & ALTER_DROP_STORED_COLUMN) { + const dict_index_t* index = ctx->new_table->indexes.start; + + for (const dict_field_t* f = index->fields, + * const end = f + index->n_fields; + f != end; f++) { + dict_col_t& c = *f->col; + if (c.is_dropped()) { + c.set_dropped(!c.is_nullable(), + DATA_LARGE_MTYPE(c.mtype) + || (!f->fixed_len + && c.len > 255), + f->fixed_len); + } + } + } + + if (!ctx->instant_table->persistent_autoinc) { + ctx->new_table->persistent_autoinc = 0; + } + } + + if (ha_alter_info->handler_flags & ALTER_COLUMN_UNVERSIONED) { + vers_change_fields_cache(ha_alter_info, ctx, table); + } + + if (ha_alter_info->handler_flags & ALTER_RENAME_INDEX) { + innobase_rename_indexes_cache(ctx, ha_alter_info); + } + + ctx->new_table->fts_doc_id_index + = ctx->new_table->fts + ? dict_table_get_index_on_name( + ctx->new_table, FTS_DOC_ID_INDEX_NAME) + : NULL; + DBUG_ASSERT((ctx->new_table->fts == NULL) + == (ctx->new_table->fts_doc_id_index == NULL)); + if (table->found_next_number_field + && !altered_table->found_next_number_field) { + ctx->prebuilt->table->persistent_autoinc = 0; + } + DBUG_RETURN(found); +} + +/** Adjust the persistent statistics after non-rebuilding ALTER TABLE. +Remove statistics for dropped indexes, add statistics for created indexes +and rename statistics for renamed indexes. +@param ha_alter_info Data used during in-place alter +@param ctx In-place ALTER TABLE context +@param thd MySQL connection +*/ +static +void +alter_stats_norebuild( +/*==================*/ + Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx* ctx, + THD* thd) +{ + DBUG_ENTER("alter_stats_norebuild"); + DBUG_ASSERT(!ctx->need_rebuild()); + + if (!dict_stats_is_persistent_enabled(ctx->new_table)) { + DBUG_VOID_RETURN; + } + + for (ulint i = 0; i < ctx->num_to_add_index; i++) { + dict_index_t* index = ctx->add_index[i]; + DBUG_ASSERT(index->table == ctx->new_table); + + if (!(index->type & DICT_FTS)) { + dict_stats_init(ctx->new_table); + dict_stats_update_for_index(index); + } + } + + DBUG_VOID_RETURN; +} + +/** Adjust the persistent statistics after rebuilding ALTER TABLE. +Remove statistics for dropped indexes, add statistics for created indexes +and rename statistics for renamed indexes. +@param table InnoDB table that was rebuilt by ALTER TABLE +@param table_name Table name in MySQL +@param thd MySQL connection +*/ +static +void +alter_stats_rebuild( +/*================*/ + dict_table_t* table, + const char* table_name, + THD* thd) +{ + DBUG_ENTER("alter_stats_rebuild"); + + if (!table->space + || !dict_stats_is_persistent_enabled(table)) { + DBUG_VOID_RETURN; + } + + dberr_t ret = dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT); + + if (ret != DB_SUCCESS) { + push_warning_printf( + thd, + Sql_condition::WARN_LEVEL_WARN, + ER_ALTER_INFO, + "Error updating stats for table '%s'" + " after table rebuild: %s", + table_name, ut_strerr(ret)); + } + + DBUG_VOID_RETURN; +} + +/** Apply the log for the table rebuild operation. +@param[in] ctx Inplace Alter table context +@param[in] altered_table MySQL table that is being altered +@return true Failure, else false. */ +static bool alter_rebuild_apply_log( + ha_innobase_inplace_ctx* ctx, + Alter_inplace_info* ha_alter_info, + TABLE* altered_table) +{ + DBUG_ENTER("alter_rebuild_apply_log"); + + if (!ctx->online) { + DBUG_RETURN(false); + } + + /* We copied the table. Any indexes that were requested to be + dropped were not created in the copy of the table. Apply any + last bit of the rebuild log and then rename the tables. */ + dict_table_t* user_table = ctx->old_table; + + DEBUG_SYNC_C("row_log_table_apply2_before"); + + dict_vcol_templ_t* s_templ = NULL; + + if (ctx->new_table->n_v_cols > 0) { + s_templ = UT_NEW_NOKEY( + dict_vcol_templ_t()); + s_templ->vtempl = NULL; + + innobase_build_v_templ(altered_table, ctx->new_table, s_templ, + NULL, true); + ctx->new_table->vc_templ = s_templ; + } + + dberr_t error = row_log_table_apply( + ctx->thr, user_table, altered_table, + static_cast<ha_innobase_inplace_ctx*>( + ha_alter_info->handler_ctx)->m_stage, + ctx->new_table); + + if (s_templ) { + ut_ad(ctx->need_rebuild()); + dict_free_vc_templ(s_templ); + UT_DELETE(s_templ); + ctx->new_table->vc_templ = NULL; + } + + DBUG_RETURN(ctx->log_failure( + ha_alter_info, altered_table, error)); +} + +/** Commit or rollback the changes made during +prepare_inplace_alter_table() and inplace_alter_table() inside +the storage engine. Note that the allowed level of concurrency +during this operation will be the same as for +inplace_alter_table() and thus might be higher than during +prepare_inplace_alter_table(). (E.g concurrent writes were +blocked during prepare, but might not be during commit). +@param altered_table TABLE object for new version of table. +@param ha_alter_info Structure describing changes to be done +by ALTER TABLE and holding data used during in-place alter. +@param commit true => Commit, false => Rollback. +@retval true Failure +@retval false Success +*/ + +bool +ha_innobase::commit_inplace_alter_table( +/*====================================*/ + TABLE* altered_table, + Alter_inplace_info* ha_alter_info, + bool commit) +{ + ha_innobase_inplace_ctx*ctx0; + + ctx0 = static_cast<ha_innobase_inplace_ctx*> + (ha_alter_info->handler_ctx); + +#ifndef DBUG_OFF + uint failure_inject_count = 1; +#endif /* DBUG_OFF */ + + DBUG_ENTER("commit_inplace_alter_table"); + DBUG_ASSERT(!srv_read_only_mode); + DBUG_ASSERT(!ctx0 || ctx0->prebuilt == m_prebuilt); + DBUG_ASSERT(!ctx0 || ctx0->old_table == m_prebuilt->table); + + DEBUG_SYNC_C("innodb_commit_inplace_alter_table_enter"); + + DEBUG_SYNC_C("innodb_commit_inplace_alter_table_wait"); + + if (ctx0 != NULL && ctx0->m_stage != NULL) { + ctx0->m_stage->begin_phase_end(); + } + + if (!commit) { + /* A rollback is being requested. So far we may at + most have created stubs for ADD INDEX or a copy of the + table for rebuild. */ + DBUG_RETURN(rollback_inplace_alter_table( + ha_alter_info, table, m_prebuilt)); + } + + if (!(ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE)) { + DBUG_ASSERT(!ctx0); + MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE); + if (table->found_next_number_field + && !altered_table->found_next_number_field) { + m_prebuilt->table->persistent_autoinc = 0; + /* Don't reset ha_alter_info->group_commit_ctx to make + partitions engine to call this function for all + partitions. */ + } + else + ha_alter_info->group_commit_ctx = NULL; + DBUG_RETURN(false); + } + + DBUG_ASSERT(ctx0); + + inplace_alter_handler_ctx** ctx_array; + inplace_alter_handler_ctx* ctx_single[2]; + + if (ha_alter_info->group_commit_ctx) { + ctx_array = ha_alter_info->group_commit_ctx; + } else { + ctx_single[0] = ctx0; + ctx_single[1] = NULL; + ctx_array = ctx_single; + } + + DBUG_ASSERT(ctx0 == ctx_array[0]); + ut_ad(m_prebuilt->table == ctx0->old_table); + ha_alter_info->group_commit_ctx = NULL; + + const bool new_clustered = ctx0->need_rebuild(); + trx_t* const trx = ctx0->trx; + trx->op_info = "acquiring table lock"; + bool fts_exist = false; + for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx; pctx++) { + auto ctx = static_cast<ha_innobase_inplace_ctx*>(*pctx); + DBUG_ASSERT(ctx->prebuilt->trx == m_prebuilt->trx); + ut_ad(m_prebuilt != ctx->prebuilt || ctx == ctx0); + DBUG_ASSERT(new_clustered == ctx->need_rebuild()); + /* If decryption failed for old table or new table + fail here. */ + if ((!ctx->old_table->is_readable() + && ctx->old_table->space) + || (!ctx->new_table->is_readable() + && ctx->new_table->space)) { + String str; + const char* engine= table_type(); + get_error_message(HA_ERR_DECRYPTION_FAILED, &str); + my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_DECRYPTION_FAILED, str.c_ptr(), engine); + DBUG_RETURN(true); + } + if ((ctx->old_table->flags2 | ctx->new_table->flags2) + & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)) { + fts_exist = true; + } + } + + bool already_stopped= false; + for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx; pctx++) { + auto ctx = static_cast<ha_innobase_inplace_ctx*>(*pctx); + dberr_t error = DB_SUCCESS; + + if (fts_exist) { + purge_sys.stop_FTS(*ctx->old_table, already_stopped); + already_stopped = true; + } + + if (new_clustered && ctx->old_table->fts) { + ut_ad(!ctx->old_table->fts->add_wq); + fts_optimize_remove_table(ctx->old_table); + } + + dict_sys.freeze(SRW_LOCK_CALL); + for (auto f : ctx->old_table->referenced_set) { + if (dict_table_t* child = f->foreign_table) { + error = lock_table_for_trx(child, trx, LOCK_X); + if (error != DB_SUCCESS) { + break; + } + } + } + dict_sys.unfreeze(); + + if (ctx->new_table->fts) { + ut_ad(!ctx->new_table->fts->add_wq); + fts_optimize_remove_table(ctx->new_table); + fts_sync_during_ddl(ctx->new_table); + } + + /* Exclusively lock the table, to ensure that no other + transaction is holding locks on the table while we + change the table definition. Any recovered incomplete + transactions would be holding InnoDB locks only, not MDL. */ + if (error == DB_SUCCESS) { + error = lock_table_for_trx(ctx->new_table, trx, + LOCK_X); + } + + DBUG_EXECUTE_IF("deadlock_table_fail", + { + error= DB_DEADLOCK; + trx_rollback_for_mysql(trx); + }); + + if (error != DB_SUCCESS) { +lock_fail: + my_error_innodb( + error, table_share->table_name.str, 0); + if (fts_exist) { + purge_sys.resume_FTS(); + } + + /* Deadlock encountered and rollbacked the + transaction. So restart the transaction + to remove the newly created table or + index from data dictionary and table cache + in rollback_inplace_alter_table() */ + if (trx->state == TRX_STATE_NOT_STARTED) { + trx_start_for_ddl(trx); + } + + DBUG_RETURN(true); + } else if ((ctx->new_table->flags2 + & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)) + && (error = fts_lock_tables(trx, *ctx->new_table)) + != DB_SUCCESS) { + goto lock_fail; + } else if (!new_clustered) { + } else if ((error = lock_table_for_trx(ctx->old_table, trx, + LOCK_X)) + != DB_SUCCESS) { + goto lock_fail; + } else if ((ctx->old_table->flags2 + & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)) + && (error = fts_lock_tables(trx, *ctx->old_table)) + != DB_SUCCESS) { + goto lock_fail; + } + } + + DEBUG_SYNC(m_user_thd, "innodb_alter_commit_after_lock_table"); + + if (new_clustered) { + /* We are holding MDL_EXCLUSIVE as well as exclusive + InnoDB table locks. Let us apply any table rebuild log + before locking dict_sys. */ + for (inplace_alter_handler_ctx** pctx= ctx_array; *pctx; + pctx++) { + auto ctx= static_cast<ha_innobase_inplace_ctx*>(*pctx); + DBUG_ASSERT(ctx->need_rebuild()); + if (alter_rebuild_apply_log(ctx, ha_alter_info, + altered_table)) { + if (fts_exist) { + purge_sys.resume_FTS(); + } + DBUG_RETURN(true); + } + } + } else { + dberr_t error= DB_SUCCESS; + for (inplace_alter_handler_ctx** pctx= ctx_array; *pctx; + pctx++) { + auto ctx= static_cast<ha_innobase_inplace_ctx*>(*pctx); + + if (!ctx->online || !ctx->old_table->space + || !ctx->old_table->is_readable()) { + continue; + } + + for (ulint i = 0; i < ctx->num_to_add_index; i++) { + dict_index_t *index= ctx->add_index[i]; + + ut_ad(!(index->type & + (DICT_FTS | DICT_SPATIAL))); + + index->lock.x_lock(SRW_LOCK_CALL); + if (!index->online_log) { + /* online log would've cleared + when we detect the error in + other index */ + index->lock.x_unlock(); + continue; + } + + if (index->is_corrupted()) { + /* Online index log has been + preserved to show the error + when it happened via + row_log_apply() by DML thread */ + error= row_log_get_error(index); +err_index: + ut_ad(error != DB_SUCCESS); + ctx->log_failure( + ha_alter_info, + altered_table, error); + row_log_free(index->online_log); + index->online_log= nullptr; + index->lock.x_unlock(); + + ctx->old_table->indexes.start + ->online_log= nullptr; + if (fts_exist) { + purge_sys.resume_FTS(); + } + MONITOR_ATOMIC_INC( + MONITOR_BACKGROUND_DROP_INDEX); + DBUG_RETURN(true); + } + + index->lock.x_unlock(); + + error = row_log_apply( + m_prebuilt->trx, index, altered_table, + ctx->m_stage); + + index->lock.x_lock(SRW_LOCK_CALL); + + if (error != DB_SUCCESS) { + goto err_index; + } + + row_log_free(index->online_log); + index->online_log= nullptr; + index->lock.x_unlock(); + } + + ctx->old_table->indexes.start->online_log= nullptr; + } + } + + dict_table_t *table_stats = nullptr, *index_stats = nullptr; + MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr; + dberr_t error = DB_SUCCESS; + if (!ctx0->old_table->is_stats_table() && + !ctx0->new_table->is_stats_table()) { + table_stats = dict_table_open_on_name( + TABLE_STATS_NAME, false, DICT_ERR_IGNORE_NONE); + if (table_stats) { + dict_sys.freeze(SRW_LOCK_CALL); + table_stats = dict_acquire_mdl_shared<false>( + table_stats, m_user_thd, &mdl_table); + dict_sys.unfreeze(); + } + index_stats = dict_table_open_on_name( + INDEX_STATS_NAME, false, DICT_ERR_IGNORE_NONE); + if (index_stats) { + dict_sys.freeze(SRW_LOCK_CALL); + index_stats = dict_acquire_mdl_shared<false>( + index_stats, m_user_thd, &mdl_index); + dict_sys.unfreeze(); + } + + if (table_stats && index_stats + && !strcmp(table_stats->name.m_name, TABLE_STATS_NAME) + && !strcmp(index_stats->name.m_name, INDEX_STATS_NAME) + && !(error = lock_table_for_trx(table_stats, + trx, LOCK_X))) { + error = lock_table_for_trx(index_stats, trx, LOCK_X); + } + } + + DBUG_EXECUTE_IF("stats_lock_fail", + error = DB_LOCK_WAIT_TIMEOUT; + trx_rollback_for_mysql(trx);); + + if (error == DB_SUCCESS) { + error = lock_sys_tables(trx); + } + if (error != DB_SUCCESS) { + if (table_stats) { + dict_table_close(table_stats, false, m_user_thd, + mdl_table); + } + if (index_stats) { + dict_table_close(index_stats, false, m_user_thd, + mdl_index); + } + my_error_innodb(error, table_share->table_name.str, 0); + if (fts_exist) { + purge_sys.resume_FTS(); + } + + if (trx->state == TRX_STATE_NOT_STARTED) { + /* Transaction may have been rolled back + due to a lock wait timeout, deadlock, + or a KILL statement. So restart the + transaction to remove the newly created + table or index stubs from data dictionary + and table cache in + rollback_inplace_alter_table() */ + trx_start_for_ddl(trx); + } + + DBUG_RETURN(true); + } + + row_mysql_lock_data_dictionary(trx); + + /* Apply the changes to the data dictionary tables, for all + partitions. */ + for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx; pctx++) { + auto ctx = static_cast<ha_innobase_inplace_ctx*>(*pctx); + + DBUG_ASSERT(new_clustered == ctx->need_rebuild()); + if (ctx->need_rebuild() && !ctx->old_table->space) { + my_error(ER_TABLESPACE_DISCARDED, MYF(0), + table->s->table_name.str); +fail: + trx->rollback(); + ut_ad(!trx->fts_trx); + if (table_stats) { + dict_table_close(table_stats, true, m_user_thd, + mdl_table); + } + if (index_stats) { + dict_table_close(index_stats, true, m_user_thd, + mdl_index); + } + row_mysql_unlock_data_dictionary(trx); + if (fts_exist) { + purge_sys.resume_FTS(); + } + trx_start_for_ddl(trx); + DBUG_RETURN(true); + } + + if (commit_set_autoinc(ha_alter_info, ctx, + altered_table, table)) { + goto fail; + } + + if (ctx->need_rebuild()) { + ctx->tmp_name = dict_mem_create_temporary_tablename( + ctx->heap, ctx->new_table->name.m_name, + ctx->new_table->id); + + if (commit_try_rebuild(ha_alter_info, ctx, + altered_table, table, + table_stats && index_stats, + trx, + table_share->table_name.str)) { + goto fail; + } + } else if (commit_try_norebuild(ha_alter_info, ctx, + altered_table, table, trx, + table_share->table_name.str)) { + goto fail; + } +#ifndef DBUG_OFF + { + /* Generate a dynamic dbug text. */ + char buf[32]; + + snprintf(buf, sizeof buf, + "ib_commit_inplace_fail_%u", + failure_inject_count++); + + DBUG_EXECUTE_IF(buf, + my_error(ER_INTERNAL_ERROR, MYF(0), + "Injected error!"); + goto fail; + ); + } +#endif + } + + if (table_stats) { + dict_table_close(table_stats, true, m_user_thd, mdl_table); + } + if (index_stats) { + dict_table_close(index_stats, true, m_user_thd, mdl_index); + } + + /* Commit or roll back the changes to the data dictionary. */ + DEBUG_SYNC(m_user_thd, "innodb_alter_inplace_before_commit"); + + if (new_clustered) { + ut_ad(trx->has_logged_persistent()); + for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx; + pctx++) { + auto ctx= static_cast<ha_innobase_inplace_ctx*>(*pctx); + ut_ad(!strcmp(ctx->old_table->name.m_name, + ctx->tmp_name)); + ut_ad(ctx->new_table->get_ref_count() == 1); + const bool own = m_prebuilt == ctx->prebuilt; + trx_t* const user_trx = m_prebuilt->trx; + ctx->prebuilt->table->release(); + ctx->prebuilt->table = nullptr; + row_prebuilt_free(ctx->prebuilt); + /* Rebuild the prebuilt object. */ + ctx->prebuilt = row_create_prebuilt( + ctx->new_table, altered_table->s->reclength); + if (own) { + m_prebuilt = ctx->prebuilt; + } + trx_start_if_not_started(user_trx, true); + m_prebuilt->trx = user_trx; + } + } + + ut_ad(!trx->fts_trx); + + std::vector<pfs_os_file_t> deleted; + DBUG_EXECUTE_IF("innodb_alter_commit_crash_before_commit", + log_buffer_flush_to_disk(); DBUG_SUICIDE();); + /* The SQL layer recovery of ALTER TABLE will invoke + innodb_check_version() to know whether our trx->id, which we + reported via ha_innobase::table_version() after + ha_innobase::prepare_inplace_alter_table(), was committed. + + If this trx was committed (the log write below completed), + we will be able to recover our trx->id to + dict_table_t::def_trx_id from the data dictionary tables. + + For this logic to work, purge_sys.stop_SYS() and + purge_sys.resume_SYS() will ensure that the DB_TRX_ID that we + wrote to the SYS_ tables will be preserved until the SQL layer + has durably marked the ALTER TABLE operation as completed. + + During recovery, the purge of InnoDB transaction history will + not start until innodb_ddl_recovery_done(). */ + ha_alter_info->inplace_alter_table_committed = purge_sys.resume_SYS; + purge_sys.stop_SYS(); + trx->commit(deleted); + + /* At this point, the changes to the persistent storage have + been committed or rolled back. What remains to be done is to + update the in-memory structures, close some handles, release + temporary files, and (unless we rolled back) update persistent + statistics. */ + for (inplace_alter_handler_ctx** pctx = ctx_array; + *pctx; pctx++) { + ha_innobase_inplace_ctx* ctx + = static_cast<ha_innobase_inplace_ctx*>(*pctx); + + DBUG_ASSERT(ctx->need_rebuild() == new_clustered); + + innobase_copy_frm_flags_from_table_share( + ctx->new_table, altered_table->s); + + if (new_clustered) { + DBUG_PRINT("to_be_dropped", + ("table: %s", ctx->old_table->name.m_name)); + + if (innobase_update_foreign_cache(ctx, m_user_thd) + != DB_SUCCESS + && m_prebuilt->trx->check_foreigns) { +foreign_fail: + push_warning_printf( + m_user_thd, + Sql_condition::WARN_LEVEL_WARN, + ER_ALTER_INFO, + "failed to load FOREIGN KEY" + " constraints"); + } + } else { + bool fk_fail = innobase_update_foreign_cache( + ctx, m_user_thd) != DB_SUCCESS; + + if (!commit_cache_norebuild(ha_alter_info, ctx, + altered_table, table, + trx)) { + fk_fail = true; + } + + if (fk_fail && m_prebuilt->trx->check_foreigns) { + goto foreign_fail; + } + } + + dict_mem_table_free_foreign_vcol_set(ctx->new_table); + dict_mem_table_fill_foreign_vcol_set(ctx->new_table); + } + + ut_ad(trx == ctx0->trx); + ctx0->trx = nullptr; + + /* Free the ctx->trx of other partitions, if any. We will only + use the ctx0->trx here. Others may have been allocated in + the prepare stage. */ + + for (inplace_alter_handler_ctx** pctx = &ctx_array[1]; *pctx; + pctx++) { + ha_innobase_inplace_ctx* ctx + = static_cast<ha_innobase_inplace_ctx*>(*pctx); + + if (ctx->trx) { + ctx->trx->rollback(); + ctx->trx->free(); + ctx->trx = NULL; + } + } + + /* MDEV-17468: Avoid this at least when ctx->is_instant(). + Currently dict_load_column_low() is the only place where + num_base for virtual columns is assigned to nonzero. */ + if (ctx0->num_to_drop_vcol || ctx0->num_to_add_vcol + || (ctx0->new_table->n_v_cols && !new_clustered + && (ha_alter_info->alter_info->drop_list.elements + || ha_alter_info->alter_info->create_list.elements)) + || (ctx0->is_instant() + && m_prebuilt->table->n_v_cols + && ha_alter_info->handler_flags & ALTER_STORED_COLUMN_ORDER) + || !ctx0->change_col_collate.empty()) { + DBUG_ASSERT(ctx0->old_table->get_ref_count() == 1); + ut_ad(ctx0->prebuilt == m_prebuilt); + + for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx; + pctx++) { + auto ctx= static_cast<ha_innobase_inplace_ctx*>(*pctx); + ctx->prebuilt->table = innobase_reload_table( + m_user_thd, ctx->prebuilt->table, + table->s->table_name, *ctx); + innobase_copy_frm_flags_from_table_share( + ctx->prebuilt->table, altered_table->s); + } + + unlock_and_close_files(deleted, trx); + log_write_up_to(trx->commit_lsn, true); + DBUG_EXECUTE_IF("innodb_alter_commit_crash_after_commit", + DBUG_SUICIDE();); + trx->free(); + if (fts_exist) { + purge_sys.resume_FTS(); + } + MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE); + /* There is no need to reset dict_table_t::persistent_autoinc + as the table is reloaded */ + DBUG_RETURN(false); + } + + for (inplace_alter_handler_ctx** pctx = ctx_array; + *pctx; pctx++) { + ha_innobase_inplace_ctx* ctx + = static_cast<ha_innobase_inplace_ctx*> + (*pctx); + DBUG_ASSERT(ctx->need_rebuild() == new_clustered); + + /* Publish the created fulltext index, if any. + Note that a fulltext index can be created without + creating the clustered index, if there already exists + a suitable FTS_DOC_ID column. If not, one will be + created, implying new_clustered */ + for (ulint i = 0; i < ctx->num_to_add_index; i++) { + dict_index_t* index = ctx->add_index[i]; + + if (index->type & DICT_FTS) { + DBUG_ASSERT(index->type == DICT_FTS); + /* We reset DICT_TF2_FTS here because the bit + is left unset when a drop proceeds the add. */ + DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS); + fts_add_index(index, ctx->new_table); + } + } + + ut_d(dict_table_check_for_dup_indexes( + ctx->new_table, CHECK_ALL_COMPLETE)); + + /* Start/Restart the FTS background operations. */ + if (ctx->new_table->fts) { + fts_optimize_add_table(ctx->new_table); + } + + ut_d(dict_table_check_for_dup_indexes( + ctx->new_table, CHECK_ABORTED_OK)); + +#ifdef UNIV_DEBUG + if (!(ctx->new_table->fts != NULL + && ctx->new_table->fts->cache->sync->in_progress)) { + ut_a(fts_check_cached_index(ctx->new_table)); + } +#endif + } + + unlock_and_close_files(deleted, trx); + log_write_up_to(trx->commit_lsn, true); + DBUG_EXECUTE_IF("innodb_alter_commit_crash_after_commit", + DBUG_SUICIDE();); + trx->free(); + if (fts_exist) { + purge_sys.resume_FTS(); + } + + /* TODO: The following code could be executed + while allowing concurrent access to the table + (MDL downgrade). */ + + if (new_clustered) { + for (inplace_alter_handler_ctx** pctx = ctx_array; + *pctx; pctx++) { + ha_innobase_inplace_ctx* ctx + = static_cast<ha_innobase_inplace_ctx*> + (*pctx); + DBUG_ASSERT(ctx->need_rebuild()); + + alter_stats_rebuild( + ctx->new_table, table->s->table_name.str, + m_user_thd); + } + } else { + for (inplace_alter_handler_ctx** pctx = ctx_array; + *pctx; pctx++) { + ha_innobase_inplace_ctx* ctx + = static_cast<ha_innobase_inplace_ctx*> + (*pctx); + DBUG_ASSERT(!ctx->need_rebuild()); + + alter_stats_norebuild(ha_alter_info, ctx, m_user_thd); + } + } + + innobase_parse_hint_from_comment( + m_user_thd, m_prebuilt->table, altered_table->s); + + /* TODO: Also perform DROP TABLE and DROP INDEX after + the MDL downgrade. */ + +#ifndef DBUG_OFF + dict_index_t* clust_index = dict_table_get_first_index( + ctx0->prebuilt->table); + DBUG_ASSERT(!clust_index->online_log); + DBUG_ASSERT(dict_index_get_online_status(clust_index) + == ONLINE_INDEX_COMPLETE); + + for (dict_index_t* index = clust_index; + index; + index = dict_table_get_next_index(index)) { + DBUG_ASSERT(!index->to_be_dropped); + } +#endif /* DBUG_OFF */ + MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE); + DBUG_RETURN(false); +} + +/** +@param thd the session +@param start_value the lower bound +@param max_value the upper bound (inclusive) */ + +ib_sequence_t::ib_sequence_t( + THD* thd, + ulonglong start_value, + ulonglong max_value) + : + m_max_value(max_value), + m_increment(0), + m_offset(0), + m_next_value(start_value), + m_eof(false) +{ + if (thd != 0 && m_max_value > 0) { + + thd_get_autoinc(thd, &m_offset, &m_increment); + + if (m_increment > 1 || m_offset > 1) { + + /* If there is an offset or increment specified + then we need to work out the exact next value. */ + + m_next_value = innobase_next_autoinc( + start_value, 1, + m_increment, m_offset, m_max_value); + + } else if (start_value == 0) { + /* The next value can never be 0. */ + m_next_value = 1; + } + } else { + m_eof = true; + } +} + +/** +Postfix increment +@return the next value to insert */ + +ulonglong +ib_sequence_t::operator++(int) UNIV_NOTHROW +{ + ulonglong current = m_next_value; + + ut_ad(!m_eof); + ut_ad(m_max_value > 0); + + m_next_value = innobase_next_autoinc( + current, 1, m_increment, m_offset, m_max_value); + + if (m_next_value == m_max_value && current == m_next_value) { + m_eof = true; + } + + return(current); +} diff --git a/storage/innobase/handler/i_s.cc b/storage/innobase/handler/i_s.cc new file mode 100644 index 00000000..b00308d7 --- /dev/null +++ b/storage/innobase/handler/i_s.cc @@ -0,0 +1,6506 @@ +/***************************************************************************** + +Copyright (c) 2007, 2016, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2014, 2022, MariaDB Corporation. + +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 Street, Fifth Floor, Boston, MA 02110-1335 USA + +*****************************************************************************/ + +/**************************************************//** +@file handler/i_s.cc +InnoDB INFORMATION SCHEMA tables interface to MySQL. + +Created July 18, 2007 Vasil Dimov +*******************************************************/ + +#include "univ.i" +#include <mysql_version.h> +#include <field.h> + +#include <sql_acl.h> +#include <sql_show.h> +#include <sql_time.h> + +#include "i_s.h" +#include "btr0pcur.h" +#include "btr0types.h" +#include "dict0dict.h" +#include "dict0load.h" +#include "buf0buddy.h" +#include "buf0buf.h" +#include "ibuf0ibuf.h" +#include "dict0mem.h" +#include "dict0types.h" +#include "srv0start.h" +#include "trx0i_s.h" +#include "trx0trx.h" +#include "srv0mon.h" +#include "pars0pars.h" +#include "fts0types.h" +#include "fts0opt.h" +#include "fts0priv.h" +#include "btr0btr.h" +#include "page0zip.h" +#include "fil0fil.h" +#include "fil0crypt.h" +#include "dict0crea.h" +#include "fts0vlc.h" +#include "scope.h" +#include "log.h" + +/** The latest successfully looked up innodb_fts_aux_table */ +table_id_t innodb_ft_aux_table_id; + +/** structure associates a name string with a file page type and/or buffer +page state. */ +struct buf_page_desc_t{ + const char* type_str; /*!< String explain the page + type/state */ + ulint type_value; /*!< Page type or page state */ +}; + +/** We also define I_S_PAGE_TYPE_INDEX as the Index Page's position +in i_s_page_type[] array */ +#define I_S_PAGE_TYPE_INDEX 1 + +/** Any unassigned FIL_PAGE_TYPE will be treated as unknown. */ +#define I_S_PAGE_TYPE_UNKNOWN FIL_PAGE_TYPE_UNKNOWN + +/** R-tree index page */ +#define I_S_PAGE_TYPE_RTREE (FIL_PAGE_TYPE_LAST + 1) + +/** Change buffer B-tree page */ +#define I_S_PAGE_TYPE_IBUF (FIL_PAGE_TYPE_LAST + 2) + +#define I_S_PAGE_TYPE_LAST I_S_PAGE_TYPE_IBUF + +#define I_S_PAGE_TYPE_BITS 4 + +/** Name string for File Page Types */ +static buf_page_desc_t i_s_page_type[] = { + {"ALLOCATED", FIL_PAGE_TYPE_ALLOCATED}, + {"INDEX", FIL_PAGE_INDEX}, + {"UNDO_LOG", FIL_PAGE_UNDO_LOG}, + {"INODE", FIL_PAGE_INODE}, + {"IBUF_FREE_LIST", FIL_PAGE_IBUF_FREE_LIST}, + {"IBUF_BITMAP", FIL_PAGE_IBUF_BITMAP}, + {"SYSTEM", FIL_PAGE_TYPE_SYS}, + {"TRX_SYSTEM", FIL_PAGE_TYPE_TRX_SYS}, + {"FILE_SPACE_HEADER", FIL_PAGE_TYPE_FSP_HDR}, + {"EXTENT_DESCRIPTOR", FIL_PAGE_TYPE_XDES}, + {"BLOB", FIL_PAGE_TYPE_BLOB}, + {"COMPRESSED_BLOB", FIL_PAGE_TYPE_ZBLOB}, + {"COMPRESSED_BLOB2", FIL_PAGE_TYPE_ZBLOB2}, + {"UNKNOWN", I_S_PAGE_TYPE_UNKNOWN}, + {"RTREE_INDEX", I_S_PAGE_TYPE_RTREE}, + {"IBUF_INDEX", I_S_PAGE_TYPE_IBUF}, + {"PAGE COMPRESSED", FIL_PAGE_PAGE_COMPRESSED}, + {"PAGE COMPRESSED AND ENCRYPTED", FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED}, +}; + +/** This structure defines information we will fetch from pages +currently cached in the buffer pool. It will be used to populate +table INFORMATION_SCHEMA.INNODB_BUFFER_PAGE */ +struct buf_page_info_t{ + ulint block_id; /*!< Buffer Pool block ID */ + /** page identifier */ + page_id_t id; + uint32_t access_time; /*!< Time of first access */ + uint32_t state; /*!< buf_page_t::state() */ +#ifdef BTR_CUR_HASH_ADAPT + unsigned hashed:1; /*!< Whether hash index has been + built on this page */ +#endif /* BTR_CUR_HASH_ADAPT */ + unsigned is_old:1; /*!< TRUE if the block is in the old + blocks in buf_pool.LRU_old */ + unsigned freed_page_clock:31; /*!< the value of + buf_pool.freed_page_clock */ + unsigned zip_ssize:PAGE_ZIP_SSIZE_BITS; + /*!< Compressed page size */ + unsigned compressed_only:1; /*!< ROW_FORMAT=COMPRESSED only */ + unsigned page_type:I_S_PAGE_TYPE_BITS; /*!< Page type */ + unsigned num_recs:UNIV_PAGE_SIZE_SHIFT_MAX-2; + /*!< Number of records on Page */ + unsigned data_size:UNIV_PAGE_SIZE_SHIFT_MAX; + /*!< Sum of the sizes of the records */ + lsn_t newest_mod; /*!< Log sequence number of + the youngest modification */ + lsn_t oldest_mod; /*!< Log sequence number of + the oldest modification */ + index_id_t index_id; /*!< Index ID if a index page */ +}; + +/* +Use the following types mapping: + +C type ST_FIELD_INFO::field_type +--------------------------------- +long MYSQL_TYPE_LONGLONG +(field_length=MY_INT64_NUM_DECIMAL_DIGITS) + +long unsigned MYSQL_TYPE_LONGLONG +(field_length=MY_INT64_NUM_DECIMAL_DIGITS, field_flags=MY_I_S_UNSIGNED) + +char* MYSQL_TYPE_STRING +(field_length=n) + +float MYSQL_TYPE_FLOAT +(field_length=0 is ignored) + +void* MYSQL_TYPE_LONGLONG +(field_length=MY_INT64_NUM_DECIMAL_DIGITS, field_flags=MY_I_S_UNSIGNED) + +boolean (if else) MYSQL_TYPE_LONG +(field_length=1) + +time_t MYSQL_TYPE_DATETIME +(field_length=0 ignored) +--------------------------------- +*/ + +/** +Common function to fill any of the dynamic tables: +INFORMATION_SCHEMA.innodb_trx +INFORMATION_SCHEMA.innodb_locks +INFORMATION_SCHEMA.innodb_lock_waits +@retval false if access to the table is blocked +@retval true if something should be filled in */ +static bool trx_i_s_common_fill_table(THD *thd, TABLE_LIST *tables) +{ + DBUG_ENTER("trx_i_s_common_fill_table"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) + DBUG_RETURN(false); + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* update the cache */ + trx_i_s_cache_start_write(trx_i_s_cache); + trx_i_s_possibly_fetch_data_into_cache(trx_i_s_cache); + trx_i_s_cache_end_write(trx_i_s_cache); + + if (trx_i_s_cache_is_truncated(trx_i_s_cache)) + sql_print_warning("InnoDB: Data in %.*s truncated due to memory limit" + " of %u bytes", + int(tables->schema_table_name.length), + tables->schema_table_name.str, + TRX_I_S_MEM_LIMIT); + + DBUG_RETURN(true); +} + +/*******************************************************************//** +Unbind a dynamic INFORMATION_SCHEMA table. +@return 0 on success */ +static +int +i_s_common_deinit( +/*==============*/ + void* p); /*!< in/out: table schema object */ +/*******************************************************************//** +Auxiliary function to store time_t value in MYSQL_TYPE_DATETIME +field. +@return 0 on success */ +static +int +field_store_time_t( +/*===============*/ + Field* field, /*!< in/out: target field for storage */ + time_t time) /*!< in: value to store */ +{ + MYSQL_TIME my_time; + struct tm tm_time; + + if (time) { +#if 0 + /* use this if you are sure that `variables' and `time_zone' + are always initialized */ + thd->variables.time_zone->gmt_sec_to_TIME( + &my_time, (my_time_t) time); +#else + localtime_r(&time, &tm_time); + localtime_to_TIME(&my_time, &tm_time); + my_time.time_type = MYSQL_TIMESTAMP_DATETIME; +#endif + } else { + memset(&my_time, 0, sizeof(my_time)); + } + + /* JAN: TODO: MySQL 5.7 + return(field->store_time(&my_time, MYSQL_TIMESTAMP_DATETIME)); + */ + return(field->store_time(&my_time)); +} + +/*******************************************************************//** +Auxiliary function to store char* value in MYSQL_TYPE_STRING field. +@return 0 on success */ +static +int +field_store_string( +/*===============*/ + Field* field, /*!< in/out: target field for storage */ + const char* str) /*!< in: NUL-terminated utf-8 string, + or NULL */ +{ + if (!str) { + field->set_null(); + return 0; + } + + field->set_notnull(); + return field->store(str, uint(strlen(str)), system_charset_info); +} + +#ifdef BTR_CUR_HASH_ADAPT +# define I_S_AHI 1 /* Include the IS_HASHED column */ +#else +# define I_S_AHI 0 /* Omit the IS_HASHED column */ +#endif + +static const LEX_CSTRING isolation_level_values[] = +{ + { STRING_WITH_LEN("READ UNCOMMITTED") }, + { STRING_WITH_LEN("READ COMMITTED") }, + { STRING_WITH_LEN("REPEATABLE READ") }, + { STRING_WITH_LEN("SERIALIZABLE") } +}; + +static TypelibBuffer<4> isolation_level_values_typelib(isolation_level_values); + +namespace Show { + +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_trx */ +static ST_FIELD_INFO innodb_trx_fields_info[]= +{ +#define IDX_TRX_ID 0 + Column("trx_id", ULonglong(), NOT_NULL), + +#define IDX_TRX_STATE 1 + Column("trx_state", Varchar(13), NOT_NULL), + +#define IDX_TRX_STARTED 2 + Column("trx_started", Datetime(0), NOT_NULL), + +#define IDX_TRX_REQUESTED_LOCK_ID 3 + Column("trx_requested_lock_id", + Varchar(TRX_I_S_LOCK_ID_MAX_LEN + 1), NULLABLE), + +#define IDX_TRX_WAIT_STARTED 4 + Column("trx_wait_started", Datetime(0), NULLABLE), + +#define IDX_TRX_WEIGHT 5 + Column("trx_weight", ULonglong(), NOT_NULL), + +#define IDX_TRX_MYSQL_THREAD_ID 6 + Column("trx_mysql_thread_id", ULonglong(), NOT_NULL), + +#define IDX_TRX_QUERY 7 + Column("trx_query", Varchar(TRX_I_S_TRX_QUERY_MAX_LEN), NULLABLE), + +#define IDX_TRX_OPERATION_STATE 8 + Column("trx_operation_state", Varchar(64), NULLABLE), + +#define IDX_TRX_TABLES_IN_USE 9 + Column("trx_tables_in_use", ULonglong(), NOT_NULL), + +#define IDX_TRX_TABLES_LOCKED 10 + Column("trx_tables_locked", ULonglong(), NOT_NULL), + +#define IDX_TRX_LOCK_STRUCTS 11 + Column("trx_lock_structs", ULonglong(), NOT_NULL), + +#define IDX_TRX_LOCK_MEMORY_BYTES 12 + Column("trx_lock_memory_bytes", ULonglong(), NOT_NULL), + +#define IDX_TRX_ROWS_LOCKED 13 + Column("trx_rows_locked", ULonglong(), NOT_NULL), + +#define IDX_TRX_ROWS_MODIFIED 14 + Column("trx_rows_modified", ULonglong(), NOT_NULL), + +#define IDX_TRX_CONNCURRENCY_TICKETS 15 + Column("trx_concurrency_tickets", ULonglong(), NOT_NULL), + +#define IDX_TRX_ISOLATION_LEVEL 16 + Column("trx_isolation_level", + Enum(&isolation_level_values_typelib), NOT_NULL), + +#define IDX_TRX_UNIQUE_CHECKS 17 + Column("trx_unique_checks", SLong(1), NOT_NULL), + +#define IDX_TRX_FOREIGN_KEY_CHECKS 18 + Column("trx_foreign_key_checks", SLong(1), NOT_NULL), + +#define IDX_TRX_LAST_FOREIGN_KEY_ERROR 19 + Column("trx_last_foreign_key_error", + Varchar(TRX_I_S_TRX_FK_ERROR_MAX_LEN),NULLABLE), + +#define IDX_TRX_READ_ONLY 20 + Column("trx_is_read_only", SLong(1), NOT_NULL), + +#define IDX_TRX_AUTOCOMMIT_NON_LOCKING 21 + Column("trx_autocommit_non_locking", SLong(1), NOT_NULL), + + CEnd() +}; + +} // namespace Show + +/*******************************************************************//** +Read data from cache buffer and fill the INFORMATION_SCHEMA.innodb_trx +table with it. +@retval 0 on success +@retval 1 on failure */ +static int fill_innodb_trx_from_cache(THD *thd, TABLE_LIST *tables, Item*) +{ + ulint rows_num; + char lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1]; + ulint i; + + DBUG_ENTER("fill_innodb_trx_from_cache"); + + if (!trx_i_s_common_fill_table(thd, tables)) { + DBUG_RETURN(0); + } + + struct cache + { + cache() { trx_i_s_cache_start_read(trx_i_s_cache); } + ~cache() { trx_i_s_cache_end_read(trx_i_s_cache); } + } c; + + Field** fields = tables->table->field; + + rows_num = trx_i_s_cache_get_rows_used(trx_i_s_cache, + I_S_INNODB_TRX); + + for (i = 0; i < rows_num; i++) { + + i_s_trx_row_t* row; + + row = (i_s_trx_row_t*) + trx_i_s_cache_get_nth_row( + trx_i_s_cache, I_S_INNODB_TRX, i); + + /* trx_id */ + OK(fields[IDX_TRX_ID]->store(row->trx_id, true)); + + /* trx_state */ + OK(field_store_string(fields[IDX_TRX_STATE], + row->trx_state)); + + /* trx_started */ + OK(field_store_time_t(fields[IDX_TRX_STARTED], + (time_t) row->trx_started)); + + /* trx_requested_lock_id */ + /* trx_wait_started */ + if (row->trx_wait_started != 0) { + + OK(field_store_string( + fields[IDX_TRX_REQUESTED_LOCK_ID], + trx_i_s_create_lock_id( + row->requested_lock_row, + lock_id, sizeof(lock_id)))); + /* field_store_string() sets it no notnull */ + + OK(field_store_time_t( + fields[IDX_TRX_WAIT_STARTED], + (time_t) row->trx_wait_started)); + fields[IDX_TRX_WAIT_STARTED]->set_notnull(); + } else { + + fields[IDX_TRX_REQUESTED_LOCK_ID]->set_null(); + fields[IDX_TRX_WAIT_STARTED]->set_null(); + } + + /* trx_weight */ + OK(fields[IDX_TRX_WEIGHT]->store(row->trx_weight, true)); + + /* trx_mysql_thread_id */ + OK(fields[IDX_TRX_MYSQL_THREAD_ID]->store( + row->trx_mysql_thread_id, true)); + + /* trx_query */ + if (row->trx_query) { + /* store will do appropriate character set + conversion check */ + fields[IDX_TRX_QUERY]->store( + row->trx_query, + static_cast<uint>(strlen(row->trx_query)), + row->trx_query_cs); + fields[IDX_TRX_QUERY]->set_notnull(); + } else { + fields[IDX_TRX_QUERY]->set_null(); + } + + /* trx_operation_state */ + OK(field_store_string(fields[IDX_TRX_OPERATION_STATE], + row->trx_operation_state)); + + /* trx_tables_in_use */ + OK(fields[IDX_TRX_TABLES_IN_USE]->store( + row->trx_tables_in_use, true)); + + /* trx_tables_locked */ + OK(fields[IDX_TRX_TABLES_LOCKED]->store( + row->trx_tables_locked, true)); + + /* trx_lock_structs */ + OK(fields[IDX_TRX_LOCK_STRUCTS]->store( + row->trx_lock_structs, true)); + + /* trx_lock_memory_bytes */ + OK(fields[IDX_TRX_LOCK_MEMORY_BYTES]->store( + row->trx_lock_memory_bytes, true)); + + /* trx_rows_locked */ + OK(fields[IDX_TRX_ROWS_LOCKED]->store( + row->trx_rows_locked, true)); + + /* trx_rows_modified */ + OK(fields[IDX_TRX_ROWS_MODIFIED]->store( + row->trx_rows_modified, true)); + + /* trx_concurrency_tickets */ + OK(fields[IDX_TRX_CONNCURRENCY_TICKETS]->store(0, true)); + + /* trx_isolation_level */ + OK(fields[IDX_TRX_ISOLATION_LEVEL]->store( + 1 + row->trx_isolation_level, true)); + + /* trx_unique_checks */ + OK(fields[IDX_TRX_UNIQUE_CHECKS]->store( + row->trx_unique_checks, true)); + + /* trx_foreign_key_checks */ + OK(fields[IDX_TRX_FOREIGN_KEY_CHECKS]->store( + row->trx_foreign_key_checks, true)); + + /* trx_last_foreign_key_error */ + OK(field_store_string(fields[IDX_TRX_LAST_FOREIGN_KEY_ERROR], + row->trx_foreign_key_error)); + + /* trx_is_read_only*/ + OK(fields[IDX_TRX_READ_ONLY]->store( + row->trx_is_read_only, true)); + + /* trx_is_autocommit_non_locking */ + OK(fields[IDX_TRX_AUTOCOMMIT_NON_LOCKING]->store( + row->trx_is_autocommit_non_locking, true)); + + OK(schema_table_store_record(thd, tables->table)); + } + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_trx +@return 0 on success */ +static +int +innodb_trx_init( +/*============*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_trx_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_trx_fields_info; + schema->fill_table = fill_innodb_trx_from_cache; + + DBUG_RETURN(0); +} + +static struct st_mysql_information_schema i_s_info = +{ + MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION +}; + +/** version number reported by SHOW PLUGINS */ +constexpr unsigned i_s_version= MYSQL_VERSION_MAJOR << 8 | MYSQL_VERSION_MINOR; + +struct st_maria_plugin i_s_innodb_trx = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_TRX", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB transactions", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_trx_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +static const LEX_CSTRING lock_mode_values[] = +{ + { STRING_WITH_LEN("S") }, + { STRING_WITH_LEN("S,GAP") }, + { STRING_WITH_LEN("X") }, + { STRING_WITH_LEN("X,GAP") }, + { STRING_WITH_LEN("IS") }, + { STRING_WITH_LEN("IS,GAP") }, + { STRING_WITH_LEN("IX") }, + { STRING_WITH_LEN("IX,GAP") }, + { STRING_WITH_LEN("AUTO_INC") } +}; + +static TypelibBuffer<9> lock_mode_values_typelib(lock_mode_values); + +static const LEX_CSTRING lock_type_values[] = +{ + { STRING_WITH_LEN("RECORD") }, + { STRING_WITH_LEN("TABLE") } +}; + +static TypelibBuffer<2> lock_type_values_typelib(lock_type_values); + +namespace Show { +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_locks */ +static ST_FIELD_INFO innodb_locks_fields_info[]= +{ +#define IDX_LOCK_ID 0 + Column("lock_id", Varchar(TRX_I_S_LOCK_ID_MAX_LEN + 1), NOT_NULL), + +#define IDX_LOCK_TRX_ID 1 + Column("lock_trx_id", ULonglong(), NOT_NULL), + +#define IDX_LOCK_MODE 2 + Column("lock_mode", Enum(&lock_mode_values_typelib), NOT_NULL), + +#define IDX_LOCK_TYPE 3 + Column("lock_type", Enum(&lock_type_values_typelib), NOT_NULL), + +#define IDX_LOCK_TABLE 4 + Column("lock_table", Varchar(1024), NOT_NULL), + +#define IDX_LOCK_INDEX 5 + Column("lock_index", Varchar(1024), NULLABLE), + +#define IDX_LOCK_SPACE 6 + Column("lock_space", ULong(), NULLABLE), + +#define IDX_LOCK_PAGE 7 + Column("lock_page", ULong(), NULLABLE), + +#define IDX_LOCK_REC 8 + Column("lock_rec", ULong(), NULLABLE), + +#define IDX_LOCK_DATA 9 + Column("lock_data", Varchar(TRX_I_S_LOCK_DATA_MAX_LEN), NULLABLE), + CEnd() +}; +} // namespace Show + +/*******************************************************************//** +Read data from cache buffer and fill the INFORMATION_SCHEMA.innodb_locks +table with it. +@return 0 on success */ +static +int +fill_innodb_locks_from_cache( +/*=========================*/ + THD* thd, /*!< in: MySQL client connection */ + TABLE_LIST* tables, /*!< in/out: fill this table */ + Item*) +{ + ulint rows_num; + char lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1]; + ulint i; + + DBUG_ENTER("fill_innodb_locks_from_cache"); + + if (!trx_i_s_common_fill_table(thd, tables)) { + DBUG_RETURN(0); + } + + struct cache + { + cache() { trx_i_s_cache_start_read(trx_i_s_cache); } + ~cache() { trx_i_s_cache_end_read(trx_i_s_cache); } + } c; + + Field** fields = tables->table->field; + + rows_num = trx_i_s_cache_get_rows_used(trx_i_s_cache, + I_S_INNODB_LOCKS); + + for (i = 0; i < rows_num; i++) { + + i_s_locks_row_t* row; + char buf[MAX_FULL_NAME_LEN + 1]; + const char* bufend; + + row = (i_s_locks_row_t*) + trx_i_s_cache_get_nth_row( + trx_i_s_cache, I_S_INNODB_LOCKS, i); + + /* lock_id */ + trx_i_s_create_lock_id(row, lock_id, sizeof(lock_id)); + OK(field_store_string(fields[IDX_LOCK_ID], + lock_id)); + + /* lock_trx_id */ + OK(fields[IDX_LOCK_TRX_ID]->store(row->lock_trx_id, true)); + + /* lock_mode */ + OK(fields[IDX_LOCK_MODE]->store(row->lock_mode, true)); + + /* lock_type */ + OK(fields[IDX_LOCK_TYPE]->store( + row->lock_index ? 1 : 2, true)); + + /* lock_table */ + bufend = innobase_convert_name(buf, sizeof(buf), + row->lock_table, + strlen(row->lock_table), + thd); + OK(fields[IDX_LOCK_TABLE]->store( + buf, uint(bufend - buf), system_charset_info)); + + if (row->lock_index) { + /* record lock */ + OK(field_store_string(fields[IDX_LOCK_INDEX], + row->lock_index)); + OK(fields[IDX_LOCK_SPACE]->store( + row->lock_page.space(), true)); + fields[IDX_LOCK_SPACE]->set_notnull(); + OK(fields[IDX_LOCK_PAGE]->store( + row->lock_page.page_no(), true)); + fields[IDX_LOCK_PAGE]->set_notnull(); + OK(fields[IDX_LOCK_REC]->store( + row->lock_rec, true)); + fields[IDX_LOCK_REC]->set_notnull(); + OK(field_store_string(fields[IDX_LOCK_DATA], + row->lock_data)); + } else { + fields[IDX_LOCK_INDEX]->set_null(); + fields[IDX_LOCK_SPACE]->set_null(); + fields[IDX_LOCK_REC]->set_null(); + fields[IDX_LOCK_DATA]->set_null(); + } + + OK(schema_table_store_record(thd, tables->table)); + } + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_locks +@return 0 on success */ +static +int +innodb_locks_init( +/*==============*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_locks_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_locks_fields_info; + schema->fill_table = fill_innodb_locks_from_cache; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_locks = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_LOCKS", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB conflicting locks", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_locks_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + + +namespace Show { +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_lock_waits */ +static ST_FIELD_INFO innodb_lock_waits_fields_info[]= +{ +#define IDX_REQUESTING_TRX_ID 0 + Column("requesting_trx_id", ULonglong(), NOT_NULL), + +#define IDX_REQUESTED_LOCK_ID 1 + Column("requested_lock_id", Varchar(TRX_I_S_LOCK_ID_MAX_LEN + 1), NOT_NULL), + +#define IDX_BLOCKING_TRX_ID 2 + Column("blocking_trx_id", ULonglong(), NOT_NULL), + +#define IDX_BLOCKING_LOCK_ID 3 + Column("blocking_lock_id", Varchar(TRX_I_S_LOCK_ID_MAX_LEN + 1), NOT_NULL), + CEnd() +}; +} // namespace Show + +/*******************************************************************//** +Read data from cache buffer and fill the +INFORMATION_SCHEMA.innodb_lock_waits table with it. +@return 0 on success */ +static +int +fill_innodb_lock_waits_from_cache( +/*==============================*/ + THD* thd, /*!< in: used to call + schema_table_store_record() */ + TABLE_LIST* tables, /*!< in/out: fill this table */ + Item*) +{ + ulint rows_num; + char requested_lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1]; + char blocking_lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1]; + ulint i; + + DBUG_ENTER("fill_innodb_lock_waits_from_cache"); + + if (!trx_i_s_common_fill_table(thd, tables)) { + DBUG_RETURN(0); + } + + struct cache + { + cache() { trx_i_s_cache_start_read(trx_i_s_cache); } + ~cache() { trx_i_s_cache_end_read(trx_i_s_cache); } + } c; + + Field** fields = tables->table->field; + + rows_num = trx_i_s_cache_get_rows_used(trx_i_s_cache, + I_S_INNODB_LOCK_WAITS); + + for (i = 0; i < rows_num; i++) { + + i_s_lock_waits_row_t* row; + + row = (i_s_lock_waits_row_t*) + trx_i_s_cache_get_nth_row( + trx_i_s_cache, I_S_INNODB_LOCK_WAITS, i); + + /* requesting_trx_id */ + OK(fields[IDX_REQUESTING_TRX_ID]->store( + row->requested_lock_row->lock_trx_id, true)); + + /* requested_lock_id */ + OK(field_store_string( + fields[IDX_REQUESTED_LOCK_ID], + trx_i_s_create_lock_id( + row->requested_lock_row, + requested_lock_id, + sizeof(requested_lock_id)))); + + /* blocking_trx_id */ + OK(fields[IDX_BLOCKING_TRX_ID]->store( + row->blocking_lock_row->lock_trx_id, true)); + + /* blocking_lock_id */ + OK(field_store_string( + fields[IDX_BLOCKING_LOCK_ID], + trx_i_s_create_lock_id( + row->blocking_lock_row, + blocking_lock_id, + sizeof(blocking_lock_id)))); + + OK(schema_table_store_record(thd, tables->table)); + } + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_lock_waits +@return 0 on success */ +static +int +innodb_lock_waits_init( +/*===================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_lock_waits_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_lock_waits_fields_info; + schema->fill_table = fill_innodb_lock_waits_from_cache; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_lock_waits = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_LOCK_WAITS", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB which lock is blocking which", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_lock_waits_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/* Fields of the dynamic table information_schema.innodb_cmp. */ +static ST_FIELD_INFO i_s_cmp_fields_info[] = +{ + Column("page_size", SLong(5),NOT_NULL, "Compressed Page Size"), + Column("compress_ops", SLong(), NOT_NULL, "Total Number of Compressions"), + Column("compress_ops_ok",SLong(), NOT_NULL, "Total Number of " + "Successful Compressions"), + Column("compress_time", SLong(), NOT_NULL, "Total Duration of " + "Compressions, in Seconds"), + Column("uncompress_ops", SLong(), NOT_NULL, "Total Number of Decompressions"), + Column("uncompress_time",SLong(), NOT_NULL, "Total Duration of " + "Decompressions, in Seconds"), + CEnd(), +}; +} // namespace Show + + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_cmp or +innodb_cmp_reset. +@return 0 on success, 1 on failure */ +static +int +i_s_cmp_fill_low( +/*=============*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* , /*!< in: condition (ignored) */ + ibool reset) /*!< in: TRUE=reset cumulated counts */ +{ + TABLE* table = (TABLE*) tables->table; + int status = 0; + + DBUG_ENTER("i_s_cmp_fill_low"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + for (uint i = 0; i < PAGE_ZIP_SSIZE_MAX; i++) { + page_zip_stat_t* zip_stat = &page_zip_stat[i]; + + table->field[0]->store(UNIV_ZIP_SIZE_MIN << i); + + /* The cumulated counts are not protected by any + mutex. Thus, some operation in page0zip.cc could + increment a counter between the time we read it and + clear it. We could introduce mutex protection, but it + could cause a measureable performance hit in + page0zip.cc. */ + table->field[1]->store(zip_stat->compressed, true); + table->field[2]->store(zip_stat->compressed_ok, true); + table->field[3]->store(zip_stat->compressed_usec / 1000000, + true); + table->field[4]->store(zip_stat->decompressed, true); + table->field[5]->store(zip_stat->decompressed_usec / 1000000, + true); + + if (reset) { + new (zip_stat) page_zip_stat_t(); + } + + if (schema_table_store_record(thd, table)) { + status = 1; + break; + } + } + + DBUG_RETURN(status); +} + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_cmp. +@return 0 on success, 1 on failure */ +static +int +i_s_cmp_fill( +/*=========*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* cond) /*!< in: condition (ignored) */ +{ + return(i_s_cmp_fill_low(thd, tables, cond, FALSE)); +} + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_cmp_reset. +@return 0 on success, 1 on failure */ +static +int +i_s_cmp_reset_fill( +/*===============*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* cond) /*!< in: condition (ignored) */ +{ + return(i_s_cmp_fill_low(thd, tables, cond, TRUE)); +} + +/*******************************************************************//** +Bind the dynamic table information_schema.innodb_cmp. +@return 0 on success */ +static +int +i_s_cmp_init( +/*=========*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmp_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_cmp_fields_info; + schema->fill_table = i_s_cmp_fill; + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table information_schema.innodb_cmp_reset. +@return 0 on success */ +static +int +i_s_cmp_reset_init( +/*===============*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmp_reset_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_cmp_fields_info; + schema->fill_table = i_s_cmp_reset_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_cmp = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_CMP", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "Statistics for the InnoDB compression", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_cmp_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +struct st_maria_plugin i_s_innodb_cmp_reset = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_CMP_RESET", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "Statistics for the InnoDB compression;" + " reset cumulated counts", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_cmp_reset_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + + +namespace Show { +/* Fields of the dynamic tables +information_schema.innodb_cmp_per_index and +information_schema.innodb_cmp_per_index_reset. */ +static ST_FIELD_INFO i_s_cmp_per_index_fields_info[]= +{ +#define IDX_DATABASE_NAME 0 + Column("database_name", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define IDX_TABLE_NAME 1 /* FIXME: this is in my_charset_filename! */ + Column("table_name", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define IDX_INDEX_NAME 2 + Column("index_name", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define IDX_COMPRESS_OPS 3 + Column("compress_ops", SLong(), NOT_NULL), + +#define IDX_COMPRESS_OPS_OK 4 + Column("compress_ops_ok", SLong(), NOT_NULL), + +#define IDX_COMPRESS_TIME 5 + Column("compress_time", SLong(), NOT_NULL), + +#define IDX_UNCOMPRESS_OPS 6 + Column("uncompress_ops", SLong(), NOT_NULL), + +#define IDX_UNCOMPRESS_TIME 7 + Column("uncompress_time", SLong(), NOT_NULL), + + CEnd() +}; + +} // namespace Show + +/*******************************************************************//** +Fill the dynamic table +information_schema.innodb_cmp_per_index or +information_schema.innodb_cmp_per_index_reset. +@return 0 on success, 1 on failure */ +static +int +i_s_cmp_per_index_fill_low( +/*=======================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* , /*!< in: condition (ignored) */ + ibool reset) /*!< in: TRUE=reset cumulated counts */ +{ + TABLE* table = tables->table; + Field** fields = table->field; + int status = 0; + + DBUG_ENTER("i_s_cmp_per_index_fill_low"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* Create a snapshot of the stats so we do not bump into lock + order violations with dict_sys.latch below. */ + mysql_mutex_lock(&page_zip_stat_per_index_mutex); + page_zip_stat_per_index_t snap (page_zip_stat_per_index); + mysql_mutex_unlock(&page_zip_stat_per_index_mutex); + + dict_sys.freeze(SRW_LOCK_CALL); + + page_zip_stat_per_index_t::iterator iter; + ulint i; + + for (iter = snap.begin(), i = 0; iter != snap.end(); iter++, i++) { + + if (dict_index_t* index + = dict_index_get_if_in_cache_low(iter->first)) { + char db_utf8[MAX_DB_UTF8_LEN]; + char table_utf8[MAX_TABLE_UTF8_LEN]; + + dict_fs2utf8(index->table->name.m_name, + db_utf8, sizeof(db_utf8), + table_utf8, sizeof(table_utf8)); + + status = field_store_string(fields[IDX_DATABASE_NAME], + db_utf8) + || field_store_string(fields[IDX_TABLE_NAME], + table_utf8) + || field_store_string(fields[IDX_INDEX_NAME], + index->name); + } else { + /* index not found */ + char name[MY_INT64_NUM_DECIMAL_DIGITS + + sizeof "index_id: "]; + fields[IDX_DATABASE_NAME]->set_null(); + fields[IDX_TABLE_NAME]->set_null(); + fields[IDX_INDEX_NAME]->set_notnull(); + status = fields[IDX_INDEX_NAME]->store( + name, + uint(snprintf(name, sizeof name, + "index_id: " IB_ID_FMT, + iter->first)), + system_charset_info); + } + + if (status + || fields[IDX_COMPRESS_OPS]->store( + iter->second.compressed, true) + || fields[IDX_COMPRESS_OPS_OK]->store( + iter->second.compressed_ok, true) + || fields[IDX_COMPRESS_TIME]->store( + iter->second.compressed_usec / 1000000, true) + || fields[IDX_UNCOMPRESS_OPS]->store( + iter->second.decompressed, true) + || fields[IDX_UNCOMPRESS_TIME]->store( + iter->second.decompressed_usec / 1000000, true) + || schema_table_store_record(thd, table)) { + status = 1; + break; + } + /* Release and reacquire the dict_sys.latch to allow other + threads to proceed. This could eventually result in the + contents of INFORMATION_SCHEMA.innodb_cmp_per_index being + inconsistent, but it is an acceptable compromise. */ + if (i == 1000) { + dict_sys.unfreeze(); + i = 0; + dict_sys.freeze(SRW_LOCK_CALL); + } + } + + dict_sys.unfreeze(); + + if (reset) { + page_zip_reset_stat_per_index(); + } + + DBUG_RETURN(status); +} + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_cmp_per_index. +@return 0 on success, 1 on failure */ +static +int +i_s_cmp_per_index_fill( +/*===================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* cond) /*!< in: condition (ignored) */ +{ + return(i_s_cmp_per_index_fill_low(thd, tables, cond, FALSE)); +} + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_cmp_per_index_reset. +@return 0 on success, 1 on failure */ +static +int +i_s_cmp_per_index_reset_fill( +/*=========================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* cond) /*!< in: condition (ignored) */ +{ + return(i_s_cmp_per_index_fill_low(thd, tables, cond, TRUE)); +} + +/*******************************************************************//** +Bind the dynamic table information_schema.innodb_cmp_per_index. +@return 0 on success */ +static +int +i_s_cmp_per_index_init( +/*===================*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmp_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_cmp_per_index_fields_info; + schema->fill_table = i_s_cmp_per_index_fill; + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table information_schema.innodb_cmp_per_index_reset. +@return 0 on success */ +static +int +i_s_cmp_per_index_reset_init( +/*=========================*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmp_reset_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_cmp_per_index_fields_info; + schema->fill_table = i_s_cmp_per_index_reset_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_cmp_per_index = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_CMP_PER_INDEX", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "Statistics for the InnoDB compression (per index)", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_cmp_per_index_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +struct st_maria_plugin i_s_innodb_cmp_per_index_reset = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_CMP_PER_INDEX_RESET", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "Statistics for the InnoDB compression (per index);" + " reset cumulated counts", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_cmp_per_index_reset_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + + +namespace Show { +/* Fields of the dynamic table information_schema.innodb_cmpmem. */ +static ST_FIELD_INFO i_s_cmpmem_fields_info[] = +{ + Column("page_size", SLong(5), NOT_NULL, "Buddy Block Size"), + Column("buffer_pool_instance", SLong(), NOT_NULL, "Buffer Pool Id"), + Column("pages_used", SLong(), NOT_NULL, "Currently in Use"), + Column("pages_free", SLong(), NOT_NULL, "Currently Available"), + Column("relocation_ops", SLonglong(), NOT_NULL, "Total Number of Relocations"), + Column("relocation_time", SLong(), NOT_NULL, "Total Duration of Relocations," + " in Seconds"), + CEnd() +}; +} // namespace Show + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_cmpmem or +innodb_cmpmem_reset. +@return 0 on success, 1 on failure */ +static +int +i_s_cmpmem_fill_low( +/*================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* , /*!< in: condition (ignored) */ + ibool reset) /*!< in: TRUE=reset cumulated counts */ +{ + TABLE* table = (TABLE*) tables->table; + + DBUG_ENTER("i_s_cmpmem_fill_low"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + ulint zip_free_len_local[BUF_BUDDY_SIZES_MAX + 1]; + buf_buddy_stat_t buddy_stat_local[BUF_BUDDY_SIZES_MAX + 1]; + + /* Save buddy stats for buffer pool in local variables. */ + mysql_mutex_lock(&buf_pool.mutex); + + for (uint x = 0; x <= BUF_BUDDY_SIZES; x++) { + zip_free_len_local[x] = (x < BUF_BUDDY_SIZES) ? + UT_LIST_GET_LEN(buf_pool.zip_free[x]) : 0; + + buddy_stat_local[x] = buf_pool.buddy_stat[x]; + + if (reset) { + /* This is protected by buf_pool.mutex. */ + buf_pool.buddy_stat[x].relocated = 0; + buf_pool.buddy_stat[x].relocated_usec = 0; + } + } + + mysql_mutex_unlock(&buf_pool.mutex); + + for (uint x = 0; x <= BUF_BUDDY_SIZES; x++) { + buf_buddy_stat_t* buddy_stat = &buddy_stat_local[x]; + + Field **field = table->field; + + (*field++)->store(BUF_BUDDY_LOW << x); + (*field++)->store(0, true); + (*field++)->store(buddy_stat->used, true); + (*field++)->store(zip_free_len_local[x], true); + (*field++)->store(buddy_stat->relocated, true); + (*field)->store(buddy_stat->relocated_usec / 1000000, true); + + if (schema_table_store_record(thd, table)) { + DBUG_RETURN(1); + } + } + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_cmpmem. +@return 0 on success, 1 on failure */ +static +int +i_s_cmpmem_fill( +/*============*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* cond) /*!< in: condition (ignored) */ +{ + return(i_s_cmpmem_fill_low(thd, tables, cond, FALSE)); +} + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_cmpmem_reset. +@return 0 on success, 1 on failure */ +static +int +i_s_cmpmem_reset_fill( +/*==================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* cond) /*!< in: condition (ignored) */ +{ + return(i_s_cmpmem_fill_low(thd, tables, cond, TRUE)); +} + +/*******************************************************************//** +Bind the dynamic table information_schema.innodb_cmpmem. +@return 0 on success */ +static +int +i_s_cmpmem_init( +/*============*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmpmem_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_cmpmem_fields_info; + schema->fill_table = i_s_cmpmem_fill; + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table information_schema.innodb_cmpmem_reset. +@return 0 on success */ +static +int +i_s_cmpmem_reset_init( +/*==================*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmpmem_reset_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_cmpmem_fields_info; + schema->fill_table = i_s_cmpmem_reset_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_cmpmem = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_CMPMEM", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "Statistics for the InnoDB compressed buffer pool", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_cmpmem_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +struct st_maria_plugin i_s_innodb_cmpmem_reset = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_CMPMEM_RESET", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "Statistics for the InnoDB compressed buffer pool;" + " reset cumulated counts", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_cmpmem_reset_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + + +static const LEX_CSTRING metric_type_values[] = +{ + { STRING_WITH_LEN("value") }, + { STRING_WITH_LEN("status_counter") }, + { STRING_WITH_LEN("set_owner") }, + { STRING_WITH_LEN("set_member") }, + { STRING_WITH_LEN("counter") } +}; + +static TypelibBuffer<5> metric_type_values_typelib(metric_type_values); + +namespace Show { +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_metrics */ +static ST_FIELD_INFO innodb_metrics_fields_info[]= +{ +#define METRIC_NAME 0 + Column("NAME", Varchar(NAME_LEN + 1), NOT_NULL), + +#define METRIC_SUBSYS 1 + Column("SUBSYSTEM", Varchar(NAME_LEN + 1), NOT_NULL), + +#define METRIC_VALUE_START 2 + Column("COUNT", SLonglong(), NOT_NULL), + +#define METRIC_MAX_VALUE_START 3 + Column("MAX_COUNT", SLonglong(), NULLABLE), + +#define METRIC_MIN_VALUE_START 4 + Column("MIN_COUNT", SLonglong(), NULLABLE), + +#define METRIC_AVG_VALUE_START 5 + Column("AVG_COUNT", Float(MAX_FLOAT_STR_LENGTH), NULLABLE), + +#define METRIC_VALUE_RESET 6 + Column("COUNT_RESET", SLonglong(), NOT_NULL), + +#define METRIC_MAX_VALUE_RESET 7 + Column("MAX_COUNT_RESET", SLonglong(), NULLABLE), + +#define METRIC_MIN_VALUE_RESET 8 + Column("MIN_COUNT_RESET", SLonglong(), NULLABLE), + +#define METRIC_AVG_VALUE_RESET 9 + Column("AVG_COUNT_RESET", Float(MAX_FLOAT_STR_LENGTH), NULLABLE), + +#define METRIC_START_TIME 10 + Column("TIME_ENABLED", Datetime(0), NULLABLE), + +#define METRIC_STOP_TIME 11 + Column("TIME_DISABLED", Datetime(0), NULLABLE), + +#define METRIC_TIME_ELAPSED 12 + Column("TIME_ELAPSED", SLonglong(), NULLABLE), + +#define METRIC_RESET_TIME 13 + Column("TIME_RESET", Datetime(0), NULLABLE), + +#define METRIC_STATUS 14 + Column("ENABLED", SLong(1), NOT_NULL), + +#define METRIC_TYPE 15 + Column("TYPE", Enum(&metric_type_values_typelib), NOT_NULL), + +#define METRIC_DESC 16 + Column("COMMENT", Varchar(NAME_LEN + 1), NOT_NULL), + CEnd() +}; +} // namespace Show + +/**********************************************************************//** +Fill the information schema metrics table. +@return 0 on success */ +static +int +i_s_metrics_fill( +/*=============*/ + THD* thd, /*!< in: thread */ + TABLE* table_to_fill) /*!< in/out: fill this table */ +{ + int count; + Field** fields; + double time_diff = 0; + monitor_info_t* monitor_info; + mon_type_t min_val; + mon_type_t max_val; + + DBUG_ENTER("i_s_metrics_fill"); + fields = table_to_fill->field; + + for (count = 0; count < NUM_MONITOR; count++) { + monitor_info = srv_mon_get_info((monitor_id_t) count); + + /* A good place to sanity check the Monitor ID */ + ut_a(count == monitor_info->monitor_id); + + /* If the item refers to a Module, nothing to fill, + continue. */ + if ((monitor_info->monitor_type & MONITOR_MODULE) + || (monitor_info->monitor_type & MONITOR_HIDDEN)) { + continue; + } + + /* If this is an existing "status variable", and + its corresponding counter is still on, we need + to calculate the result from its corresponding + counter. */ + if (monitor_info->monitor_type & MONITOR_EXISTING + && MONITOR_IS_ON(count)) { + srv_mon_process_existing_counter((monitor_id_t) count, + MONITOR_GET_VALUE); + } + + /* Fill in counter's basic information */ + OK(field_store_string(fields[METRIC_NAME], + monitor_info->monitor_name)); + + OK(field_store_string(fields[METRIC_SUBSYS], + monitor_info->monitor_module)); + + OK(field_store_string(fields[METRIC_DESC], + monitor_info->monitor_desc)); + + /* Fill in counter values */ + OK(fields[METRIC_VALUE_RESET]->store( + MONITOR_VALUE(count), FALSE)); + + OK(fields[METRIC_VALUE_START]->store( + MONITOR_VALUE_SINCE_START(count), FALSE)); + + /* If the max value is MAX_RESERVED, counter max + value has not been updated. Set the column value + to NULL. */ + if (MONITOR_MAX_VALUE(count) == MAX_RESERVED + || MONITOR_MAX_MIN_NOT_INIT(count)) { + fields[METRIC_MAX_VALUE_RESET]->set_null(); + } else { + OK(fields[METRIC_MAX_VALUE_RESET]->store( + MONITOR_MAX_VALUE(count), FALSE)); + fields[METRIC_MAX_VALUE_RESET]->set_notnull(); + } + + /* If the min value is MAX_RESERVED, counter min + value has not been updated. Set the column value + to NULL. */ + if (MONITOR_MIN_VALUE(count) == MIN_RESERVED + || MONITOR_MAX_MIN_NOT_INIT(count)) { + fields[METRIC_MIN_VALUE_RESET]->set_null(); + } else { + OK(fields[METRIC_MIN_VALUE_RESET]->store( + MONITOR_MIN_VALUE(count), FALSE)); + fields[METRIC_MIN_VALUE_RESET]->set_notnull(); + } + + /* Calculate the max value since counter started */ + max_val = srv_mon_calc_max_since_start((monitor_id_t) count); + + if (max_val == MAX_RESERVED + || MONITOR_MAX_MIN_NOT_INIT(count)) { + fields[METRIC_MAX_VALUE_START]->set_null(); + } else { + OK(fields[METRIC_MAX_VALUE_START]->store( + max_val, FALSE)); + fields[METRIC_MAX_VALUE_START]->set_notnull(); + } + + /* Calculate the min value since counter started */ + min_val = srv_mon_calc_min_since_start((monitor_id_t) count); + + if (min_val == MIN_RESERVED + || MONITOR_MAX_MIN_NOT_INIT(count)) { + fields[METRIC_MIN_VALUE_START]->set_null(); + } else { + OK(fields[METRIC_MIN_VALUE_START]->store( + min_val, FALSE)); + + fields[METRIC_MIN_VALUE_START]->set_notnull(); + } + + /* If monitor has been enabled (no matter it is disabled + or not now), fill METRIC_START_TIME and METRIC_TIME_ELAPSED + field */ + if (MONITOR_FIELD(count, mon_start_time)) { + OK(field_store_time_t(fields[METRIC_START_TIME], + (time_t)MONITOR_FIELD(count, mon_start_time))); + fields[METRIC_START_TIME]->set_notnull(); + + /* If monitor is enabled, the TIME_ELAPSED is the + time difference between current and time when monitor + is enabled. Otherwise, it is the time difference + between time when monitor is enabled and time + when it is disabled */ + if (MONITOR_IS_ON(count)) { + time_diff = difftime(time(NULL), + MONITOR_FIELD(count, mon_start_time)); + } else { + time_diff = difftime( + MONITOR_FIELD(count, mon_stop_time), + MONITOR_FIELD(count, mon_start_time)); + } + + OK(fields[METRIC_TIME_ELAPSED]->store( + time_diff)); + fields[METRIC_TIME_ELAPSED]->set_notnull(); + } else { + fields[METRIC_START_TIME]->set_null(); + fields[METRIC_TIME_ELAPSED]->set_null(); + time_diff = 0; + } + + /* Unless MONITOR_NO_AVERAGE is set, we must + to calculate the average value. If this is a monitor set + owner marked by MONITOR_SET_OWNER, divide + the value by another counter (number of calls) designated + by monitor_info->monitor_related_id. + Otherwise average the counter value by the time between the + time that the counter is enabled and time it is disabled + or time it is sampled. */ + if ((monitor_info->monitor_type + & (MONITOR_NO_AVERAGE | MONITOR_SET_OWNER)) + == MONITOR_SET_OWNER + && monitor_info->monitor_related_id) { + mon_type_t value_start + = MONITOR_VALUE_SINCE_START( + monitor_info->monitor_related_id); + + if (value_start) { + OK(fields[METRIC_AVG_VALUE_START]->store( + MONITOR_VALUE_SINCE_START(count) + / value_start, FALSE)); + + fields[METRIC_AVG_VALUE_START]->set_notnull(); + } else { + fields[METRIC_AVG_VALUE_START]->set_null(); + } + + if (mon_type_t related_value = + MONITOR_VALUE(monitor_info->monitor_related_id)) { + OK(fields[METRIC_AVG_VALUE_RESET] + ->store(MONITOR_VALUE(count) + / related_value, false)); + fields[METRIC_AVG_VALUE_RESET]->set_notnull(); + } else { + fields[METRIC_AVG_VALUE_RESET]->set_null(); + } + } else if (!(monitor_info->monitor_type + & (MONITOR_NO_AVERAGE + | MONITOR_DISPLAY_CURRENT))) { + if (time_diff != 0) { + OK(fields[METRIC_AVG_VALUE_START]->store( + (double) MONITOR_VALUE_SINCE_START( + count) / time_diff)); + fields[METRIC_AVG_VALUE_START]->set_notnull(); + } else { + fields[METRIC_AVG_VALUE_START]->set_null(); + } + + if (MONITOR_FIELD(count, mon_reset_time)) { + /* calculate the time difference since last + reset */ + if (MONITOR_IS_ON(count)) { + time_diff = difftime( + time(NULL), MONITOR_FIELD( + count, mon_reset_time)); + } else { + time_diff = difftime( + MONITOR_FIELD(count, mon_stop_time), + MONITOR_FIELD(count, mon_reset_time)); + } + } else { + time_diff = 0; + } + + if (time_diff != 0) { + OK(fields[METRIC_AVG_VALUE_RESET]->store( + static_cast<double>( + MONITOR_VALUE(count)) + / time_diff)); + fields[METRIC_AVG_VALUE_RESET]->set_notnull(); + } else { + fields[METRIC_AVG_VALUE_RESET]->set_null(); + } + } else { + fields[METRIC_AVG_VALUE_START]->set_null(); + fields[METRIC_AVG_VALUE_RESET]->set_null(); + } + + if (MONITOR_IS_ON(count)) { + /* If monitor is on, the stop time will set to NULL */ + fields[METRIC_STOP_TIME]->set_null(); + + /* Display latest Monitor Reset Time only if Monitor + counter is on. */ + if (MONITOR_FIELD(count, mon_reset_time)) { + OK(field_store_time_t( + fields[METRIC_RESET_TIME], + (time_t)MONITOR_FIELD( + count, mon_reset_time))); + fields[METRIC_RESET_TIME]->set_notnull(); + } else { + fields[METRIC_RESET_TIME]->set_null(); + } + + OK(fields[METRIC_STATUS]->store(1, true)); + } else { + if (MONITOR_FIELD(count, mon_stop_time)) { + OK(field_store_time_t(fields[METRIC_STOP_TIME], + (time_t)MONITOR_FIELD(count, mon_stop_time))); + fields[METRIC_STOP_TIME]->set_notnull(); + } else { + fields[METRIC_STOP_TIME]->set_null(); + } + + fields[METRIC_RESET_TIME]->set_null(); + + OK(fields[METRIC_STATUS]->store(0, true)); + } + + uint metric_type; + + if (monitor_info->monitor_type & MONITOR_DISPLAY_CURRENT) { + metric_type = 1; /* "value" */ + } else if (monitor_info->monitor_type & MONITOR_EXISTING) { + metric_type = 2; /* "status_counter" */ + } else if (monitor_info->monitor_type & MONITOR_SET_OWNER) { + metric_type = 3; /* "set_owner" */ + } else if (monitor_info->monitor_type & MONITOR_SET_MEMBER) { + metric_type = 4; /* "set_member" */ + } else { + metric_type = 5; /* "counter" */ + } + + OK(fields[METRIC_TYPE]->store(metric_type, true)); + + OK(schema_table_store_record(thd, table_to_fill)); + } + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Function to fill information schema metrics tables. +@return 0 on success */ +static +int +i_s_metrics_fill_table( +/*===================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + DBUG_ENTER("i_s_metrics_fill_table"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + i_s_metrics_fill(thd, tables->table); + + DBUG_RETURN(0); +} +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_metrics +@return 0 on success */ +static +int +innodb_metrics_init( +/*================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_metrics_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_metrics_fields_info; + schema->fill_table = i_s_metrics_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_metrics = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_METRICS", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB Metrics Info", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_metrics_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_ft_default_stopword */ +static ST_FIELD_INFO i_s_stopword_fields_info[]= +{ +#define STOPWORD_VALUE 0 + Column("value", Varchar(TRX_ID_MAX_LEN + 1), NOT_NULL), + CEnd() +}; +} // namespace Show + +/*******************************************************************//** +Fill the dynamic table information_schema.innodb_ft_default_stopword. +@return 0 on success, 1 on failure */ +static +int +i_s_stopword_fill( +/*==============*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + Field** fields; + ulint i = 0; + TABLE* table = (TABLE*) tables->table; + + DBUG_ENTER("i_s_stopword_fill"); + + fields = table->field; + + /* Fill with server default stopword list in array + fts_default_stopword */ + while (fts_default_stopword[i]) { + OK(field_store_string(fields[STOPWORD_VALUE], + fts_default_stopword[i])); + + OK(schema_table_store_record(thd, table)); + i++; + } + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table information_schema.innodb_ft_default_stopword. +@return 0 on success */ +static +int +i_s_stopword_init( +/*==============*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_stopword_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_stopword_fields_info; + schema->fill_table = i_s_stopword_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_ft_default_stopword = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_FT_DEFAULT_STOPWORD", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "Default stopword list for InnoDB Full Text Search", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_stopword_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/* Fields of the dynamic table INFORMATION_SCHEMA.INNODB_FT_DELETED +INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED */ +static ST_FIELD_INFO i_s_fts_doc_fields_info[]= +{ +#define I_S_FTS_DOC_ID 0 + Column("DOC_ID", ULonglong(), NOT_NULL), + CEnd() +}; +} // namespace Show + +/*******************************************************************//** +Fill the dynamic table INFORMATION_SCHEMA.INNODB_FT_DELETED or +INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED +@return 0 on success, 1 on failure */ +static +int +i_s_fts_deleted_generic_fill( +/*=========================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + ibool being_deleted) /*!< in: BEING_DELTED table */ +{ + Field** fields; + TABLE* table = (TABLE*) tables->table; + trx_t* trx; + fts_table_t fts_table; + fts_doc_ids_t* deleted; + dict_table_t* user_table; + + DBUG_ENTER("i_s_fts_deleted_generic_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + MDL_ticket* mdl_ticket = nullptr; + user_table = dict_table_open_on_id( + innodb_ft_aux_table_id, false, DICT_TABLE_OP_NORMAL, + thd, &mdl_ticket); + + if (!user_table) { + DBUG_RETURN(0); + } else if (!dict_table_has_fts_index(user_table) + || !user_table->is_readable()) { + dict_table_close(user_table, false, thd, mdl_ticket); + DBUG_RETURN(0); + } + + deleted = fts_doc_ids_create(); + + trx = trx_create(); + trx->op_info = "Select for FTS DELETE TABLE"; + + FTS_INIT_FTS_TABLE(&fts_table, + (being_deleted) ? "BEING_DELETED" : "DELETED", + FTS_COMMON_TABLE, user_table); + + fts_table_fetch_doc_ids(trx, &fts_table, deleted); + + dict_table_close(user_table, false, thd, mdl_ticket); + + trx->free(); + + fields = table->field; + + int ret = 0; + + for (ulint j = 0; j < ib_vector_size(deleted->doc_ids); ++j) { + doc_id_t doc_id; + + doc_id = *(doc_id_t*) ib_vector_get_const(deleted->doc_ids, j); + + BREAK_IF(ret = fields[I_S_FTS_DOC_ID]->store(doc_id, true)); + + BREAK_IF(ret = schema_table_store_record(thd, table)); + } + + fts_doc_ids_free(deleted); + + DBUG_RETURN(ret); +} + +/*******************************************************************//** +Fill the dynamic table INFORMATION_SCHEMA.INNODB_FT_DELETED +@return 0 on success, 1 on failure */ +static +int +i_s_fts_deleted_fill( +/*=================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (ignored) */ +{ + DBUG_ENTER("i_s_fts_deleted_fill"); + + DBUG_RETURN(i_s_fts_deleted_generic_fill(thd, tables, FALSE)); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_FT_DELETED +@return 0 on success */ +static +int +i_s_fts_deleted_init( +/*=================*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_fts_deleted_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_fts_doc_fields_info; + schema->fill_table = i_s_fts_deleted_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_ft_deleted = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_FT_DELETED", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "INNODB AUXILIARY FTS DELETED TABLE", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_fts_deleted_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +/*******************************************************************//** +Fill the dynamic table INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED +@return 0 on success, 1 on failure */ +static +int +i_s_fts_being_deleted_fill( +/*=======================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (ignored) */ +{ + DBUG_ENTER("i_s_fts_being_deleted_fill"); + + DBUG_RETURN(i_s_fts_deleted_generic_fill(thd, tables, TRUE)); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED +@return 0 on success */ +static +int +i_s_fts_being_deleted_init( +/*=======================*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_fts_deleted_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_fts_doc_fields_info; + schema->fill_table = i_s_fts_being_deleted_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_ft_being_deleted = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_FT_BEING_DELETED", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "INNODB AUXILIARY FTS BEING DELETED TABLE", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_fts_being_deleted_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + + +namespace Show { +/* Fields of the dynamic table INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHED and +INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE */ +static ST_FIELD_INFO i_s_fts_index_fields_info[]= +{ +#define I_S_FTS_WORD 0 + Column("WORD", Varchar(FTS_MAX_WORD_LEN + 1), NOT_NULL), + +#define I_S_FTS_FIRST_DOC_ID 1 + Column("FIRST_DOC_ID", ULonglong(), NOT_NULL), + +#define I_S_FTS_LAST_DOC_ID 2 + Column("LAST_DOC_ID", ULonglong(), NOT_NULL), + +#define I_S_FTS_DOC_COUNT 3 + Column("DOC_COUNT", ULonglong(), NOT_NULL), + +#define I_S_FTS_ILIST_DOC_ID 4 + Column("DOC_ID", ULonglong(), NOT_NULL), + +#define I_S_FTS_ILIST_DOC_POS 5 + Column("POSITION", ULonglong(), NOT_NULL), + CEnd() +}; +} // namespace Show + +/*******************************************************************//** +Go through the Doc Node and its ilist, fill the dynamic table +INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHED for one FTS index on the table. +@return 0 on success, 1 on failure */ +static +int +i_s_fts_index_cache_fill_one_index( +/*===============================*/ + fts_index_cache_t* index_cache, /*!< in: FTS index cache */ + THD* thd, /*!< in: thread */ + fts_string_t* conv_str, /*!< in/out: buffer */ + TABLE_LIST* tables) /*!< in/out: tables to fill */ +{ + TABLE* table = (TABLE*) tables->table; + Field** fields; + CHARSET_INFO* index_charset; + const ib_rbt_node_t* rbt_node; + uint dummy_errors; + char* word_str; + + DBUG_ENTER("i_s_fts_index_cache_fill_one_index"); + + fields = table->field; + + index_charset = index_cache->charset; + conv_str->f_n_char = 0; + + int ret = 0; + + /* Go through each word in the index cache */ + for (rbt_node = rbt_first(index_cache->words); + rbt_node; + rbt_node = rbt_next(index_cache->words, rbt_node)) { + fts_tokenizer_word_t* word; + + word = rbt_value(fts_tokenizer_word_t, rbt_node); + + /* Convert word from index charset to system_charset_info */ + if (index_charset->cset != system_charset_info->cset) { + conv_str->f_n_char = my_convert( + reinterpret_cast<char*>(conv_str->f_str), + static_cast<uint32>(conv_str->f_len), + system_charset_info, + reinterpret_cast<char*>(word->text.f_str), + static_cast<uint32>(word->text.f_len), + index_charset, &dummy_errors); + ut_ad(conv_str->f_n_char <= conv_str->f_len); + conv_str->f_str[conv_str->f_n_char] = 0; + word_str = reinterpret_cast<char*>(conv_str->f_str); + } else { + word_str = reinterpret_cast<char*>(word->text.f_str); + } + + /* Decrypt the ilist, and display Dod ID and word position */ + for (ulint i = 0; i < ib_vector_size(word->nodes); i++) { + fts_node_t* node; + const byte* ptr; + ulint decoded = 0; + doc_id_t doc_id = 0; + + node = static_cast<fts_node_t*> (ib_vector_get( + word->nodes, i)); + + ptr = node->ilist; + + while (decoded < node->ilist_size) { + + doc_id += fts_decode_vlc(&ptr); + + /* Get position info */ + while (*ptr) { + + OK(field_store_string( + fields[I_S_FTS_WORD], + word_str)); + + OK(fields[I_S_FTS_FIRST_DOC_ID]->store( + node->first_doc_id, + true)); + + OK(fields[I_S_FTS_LAST_DOC_ID]->store( + node->last_doc_id, + true)); + + OK(fields[I_S_FTS_DOC_COUNT]->store( + node->doc_count, true)); + + OK(fields[I_S_FTS_ILIST_DOC_ID]->store( + doc_id, true)); + + OK(fields[I_S_FTS_ILIST_DOC_POS]->store( + fts_decode_vlc(&ptr), true)); + + OK(schema_table_store_record( + thd, table)); + } + + ++ptr; + + decoded = ptr - (byte*) node->ilist; + } + } + } + + DBUG_RETURN(ret); +} +/*******************************************************************//** +Fill the dynamic table INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHED +@return 0 on success, 1 on failure */ +static +int +i_s_fts_index_cache_fill( +/*=====================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (ignored) */ +{ + dict_table_t* user_table; + fts_cache_t* cache; + + DBUG_ENTER("i_s_fts_index_cache_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + MDL_ticket* mdl_ticket = nullptr; + user_table = dict_table_open_on_id( + innodb_ft_aux_table_id, false, DICT_TABLE_OP_NORMAL, + thd, &mdl_ticket); + + if (!user_table) { + DBUG_RETURN(0); + } + + if (!user_table->fts || !user_table->fts->cache) { + dict_table_close(user_table, false, thd, mdl_ticket); + DBUG_RETURN(0); + } + + cache = user_table->fts->cache; + + int ret = 0; + fts_string_t conv_str; + byte word[HA_FT_MAXBYTELEN + 1]; + conv_str.f_len = sizeof word; + conv_str.f_str = word; + + mysql_mutex_lock(&cache->lock); + + for (ulint i = 0; i < ib_vector_size(cache->indexes); i++) { + fts_index_cache_t* index_cache; + + index_cache = static_cast<fts_index_cache_t*> ( + ib_vector_get(cache->indexes, i)); + + BREAK_IF(ret = i_s_fts_index_cache_fill_one_index( + index_cache, thd, &conv_str, tables)); + } + + mysql_mutex_unlock(&cache->lock); + dict_table_close(user_table, false, thd, mdl_ticket); + + DBUG_RETURN(ret); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE +@return 0 on success */ +static +int +i_s_fts_index_cache_init( +/*=====================*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_fts_index_cache_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_fts_index_fields_info; + schema->fill_table = i_s_fts_index_cache_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_ft_index_cache = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_FT_INDEX_CACHE", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "INNODB AUXILIARY FTS INDEX CACHED", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_fts_index_cache_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +/*******************************************************************//** +Go through a FTS index auxiliary table, fetch its rows and fill +FTS word cache structure. +@return DB_SUCCESS on success, otherwise error code */ +static +dberr_t +i_s_fts_index_table_fill_selected( +/*==============================*/ + dict_index_t* index, /*!< in: FTS index */ + ib_vector_t* words, /*!< in/out: vector to hold + fetched words */ + ulint selected, /*!< in: selected FTS index */ + fts_string_t* word) /*!< in: word to select */ +{ + pars_info_t* info; + fts_table_t fts_table; + trx_t* trx; + que_t* graph; + dberr_t error; + fts_fetch_t fetch; + char table_name[MAX_FULL_NAME_LEN]; + + info = pars_info_create(); + + fetch.read_arg = words; + fetch.read_record = fts_optimize_index_fetch_node; + fetch.total_memory = 0; + + DBUG_EXECUTE_IF("fts_instrument_result_cache_limit", + fts_result_cache_limit = 8192; + ); + + trx = trx_create(); + + trx->op_info = "fetching FTS index nodes"; + + pars_info_bind_function(info, "my_func", fetch.read_record, &fetch); + pars_info_bind_varchar_literal(info, "word", word->f_str, word->f_len); + + FTS_INIT_INDEX_TABLE(&fts_table, fts_get_suffix(selected), + FTS_INDEX_TABLE, index); + fts_get_table_name(&fts_table, table_name); + pars_info_bind_id(info, "table_name", table_name); + + graph = fts_parse_sql( + &fts_table, info, + "DECLARE FUNCTION my_func;\n" + "DECLARE CURSOR c IS" + " SELECT word, doc_count, first_doc_id, last_doc_id," + " ilist\n" + " FROM $table_name WHERE word >= :word;\n" + "BEGIN\n" + "\n" + "OPEN c;\n" + "WHILE 1 = 1 LOOP\n" + " FETCH c INTO my_func();\n" + " IF c % NOTFOUND THEN\n" + " EXIT;\n" + " END IF;\n" + "END LOOP;\n" + "CLOSE c;"); + + for (;;) { + error = fts_eval_sql(trx, graph); + + if (UNIV_LIKELY(error == DB_SUCCESS)) { + fts_sql_commit(trx); + + break; + } else { + fts_sql_rollback(trx); + + if (error == DB_LOCK_WAIT_TIMEOUT) { + ib::warn() << "Lock wait timeout reading" + " FTS index. Retrying!"; + + trx->error_state = DB_SUCCESS; + } else { + ib::error() << "Error occurred while reading" + " FTS index: " << error; + break; + } + } + } + + que_graph_free(graph); + + trx->free(); + + if (fetch.total_memory >= fts_result_cache_limit) { + error = DB_FTS_EXCEED_RESULT_CACHE_LIMIT; + } + + return(error); +} + +/*******************************************************************//** +Free words. */ +static +void +i_s_fts_index_table_free_one_fetch( +/*===============================*/ + ib_vector_t* words) /*!< in: words fetched */ +{ + for (ulint i = 0; i < ib_vector_size(words); i++) { + fts_word_t* word; + + word = static_cast<fts_word_t*>(ib_vector_get(words, i)); + + for (ulint j = 0; j < ib_vector_size(word->nodes); j++) { + fts_node_t* node; + + node = static_cast<fts_node_t*> (ib_vector_get( + word->nodes, j)); + ut_free(node->ilist); + } + + fts_word_free(word); + } + + ib_vector_reset(words); +} + +/*******************************************************************//** +Go through words, fill INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE. +@return 0 on success, 1 on failure */ +static +int +i_s_fts_index_table_fill_one_fetch( +/*===============================*/ + CHARSET_INFO* index_charset, /*!< in: FTS index charset */ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + ib_vector_t* words, /*!< in: words fetched */ + fts_string_t* conv_str, /*!< in: string for conversion*/ + bool has_more) /*!< in: has more to fetch */ +{ + TABLE* table = (TABLE*) tables->table; + Field** fields; + uint dummy_errors; + char* word_str; + ulint words_size; + int ret = 0; + + DBUG_ENTER("i_s_fts_index_table_fill_one_fetch"); + + fields = table->field; + + words_size = ib_vector_size(words); + if (has_more) { + /* the last word is not fetched completely. */ + ut_ad(words_size > 1); + words_size -= 1; + } + + /* Go through each word in the index cache */ + for (ulint i = 0; i < words_size; i++) { + fts_word_t* word; + + word = static_cast<fts_word_t*>(ib_vector_get(words, i)); + + word->text.f_str[word->text.f_len] = 0; + + /* Convert word from index charset to system_charset_info */ + if (index_charset->cset != system_charset_info->cset) { + conv_str->f_n_char = my_convert( + reinterpret_cast<char*>(conv_str->f_str), + static_cast<uint32>(conv_str->f_len), + system_charset_info, + reinterpret_cast<char*>(word->text.f_str), + static_cast<uint32>(word->text.f_len), + index_charset, &dummy_errors); + ut_ad(conv_str->f_n_char <= conv_str->f_len); + conv_str->f_str[conv_str->f_n_char] = 0; + word_str = reinterpret_cast<char*>(conv_str->f_str); + } else { + word_str = reinterpret_cast<char*>(word->text.f_str); + } + + /* Decrypt the ilist, and display Dod ID and word position */ + for (ulint i = 0; i < ib_vector_size(word->nodes); i++) { + fts_node_t* node; + const byte* ptr; + ulint decoded = 0; + doc_id_t doc_id = 0; + + node = static_cast<fts_node_t*> (ib_vector_get( + word->nodes, i)); + + ptr = node->ilist; + + while (decoded < node->ilist_size) { + doc_id += fts_decode_vlc(&ptr); + + /* Get position info */ + while (*ptr) { + + OK(field_store_string( + fields[I_S_FTS_WORD], + word_str)); + + OK(fields[I_S_FTS_FIRST_DOC_ID]->store( + longlong(node->first_doc_id), true)); + + OK(fields[I_S_FTS_LAST_DOC_ID]->store( + longlong(node->last_doc_id), true)); + + OK(fields[I_S_FTS_DOC_COUNT]->store( + node->doc_count, true)); + + OK(fields[I_S_FTS_ILIST_DOC_ID]->store( + longlong(doc_id), true)); + + OK(fields[I_S_FTS_ILIST_DOC_POS]->store( + fts_decode_vlc(&ptr), true)); + + OK(schema_table_store_record( + thd, table)); + } + + ++ptr; + + decoded = ptr - (byte*) node->ilist; + } + } + } + + DBUG_RETURN(ret); +} + +/*******************************************************************//** +Go through a FTS index and its auxiliary tables, fetch rows in each table +and fill INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE. +@return 0 on success, 1 on failure */ +static +int +i_s_fts_index_table_fill_one_index( +/*===============================*/ + dict_index_t* index, /*!< in: FTS index */ + THD* thd, /*!< in: thread */ + fts_string_t* conv_str, /*!< in/out: buffer */ + TABLE_LIST* tables) /*!< in/out: tables to fill */ +{ + ib_vector_t* words; + mem_heap_t* heap; + CHARSET_INFO* index_charset; + dberr_t error; + int ret = 0; + + DBUG_ENTER("i_s_fts_index_table_fill_one_index"); + DBUG_ASSERT(!dict_index_is_online_ddl(index)); + + heap = mem_heap_create(1024); + + words = ib_vector_create(ib_heap_allocator_create(heap), + sizeof(fts_word_t), 256); + + index_charset = fts_index_get_charset(index); + + /* Iterate through each auxiliary table as described in + fts_index_selector */ + for (ulint selected = 0; selected < FTS_NUM_AUX_INDEX; selected++) { + fts_string_t word; + bool has_more = false; + + word.f_str = NULL; + word.f_len = 0; + word.f_n_char = 0; + + do { + /* Fetch from index */ + error = i_s_fts_index_table_fill_selected( + index, words, selected, &word); + + if (error == DB_SUCCESS) { + has_more = false; + } else if (error == DB_FTS_EXCEED_RESULT_CACHE_LIMIT) { + has_more = true; + } else { + i_s_fts_index_table_free_one_fetch(words); + ret = 1; + goto func_exit; + } + + if (has_more) { + fts_word_t* last_word; + + /* Prepare start point for next fetch */ + last_word = static_cast<fts_word_t*>(ib_vector_last(words)); + ut_ad(last_word != NULL); + fts_string_dup(&word, &last_word->text, heap); + } + + /* Fill into tables */ + ret = i_s_fts_index_table_fill_one_fetch( + index_charset, thd, tables, words, conv_str, + has_more); + i_s_fts_index_table_free_one_fetch(words); + + if (ret != 0) { + goto func_exit; + } + } while (has_more); + } + +func_exit: + mem_heap_free(heap); + + DBUG_RETURN(ret); +} +/*******************************************************************//** +Fill the dynamic table INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE +@return 0 on success, 1 on failure */ +static +int +i_s_fts_index_table_fill( +/*=====================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (ignored) */ +{ + dict_table_t* user_table; + dict_index_t* index; + + DBUG_ENTER("i_s_fts_index_table_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + MDL_ticket* mdl_ticket = nullptr; + user_table = dict_table_open_on_id( + innodb_ft_aux_table_id, false, DICT_TABLE_OP_NORMAL, + thd, &mdl_ticket); + + if (!user_table) { + DBUG_RETURN(0); + } + + int ret = 0; + fts_string_t conv_str; + conv_str.f_len = system_charset_info->mbmaxlen + * FTS_MAX_WORD_LEN_IN_CHAR; + conv_str.f_str = static_cast<byte*>(ut_malloc_nokey(conv_str.f_len)); + + for (index = dict_table_get_first_index(user_table); + index; index = dict_table_get_next_index(index)) { + if (index->type & DICT_FTS) { + BREAK_IF(ret = i_s_fts_index_table_fill_one_index( + index, thd, &conv_str, tables)); + } + } + + dict_table_close(user_table, false, thd, mdl_ticket); + + ut_free(conv_str.f_str); + + DBUG_RETURN(ret); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE +@return 0 on success */ +static +int +i_s_fts_index_table_init( +/*=====================*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_fts_index_table_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_fts_index_fields_info; + schema->fill_table = i_s_fts_index_table_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_ft_index_table = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_FT_INDEX_TABLE", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "INNODB AUXILIARY FTS INDEX TABLE", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_fts_index_table_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + + +namespace Show { +/* Fields of the dynamic table INFORMATION_SCHEMA.INNODB_FT_CONFIG */ +static ST_FIELD_INFO i_s_fts_config_fields_info[]= +{ +#define FTS_CONFIG_KEY 0 + Column("KEY", Varchar(NAME_LEN + 1), NOT_NULL), + +#define FTS_CONFIG_VALUE 1 + Column("VALUE", Varchar(NAME_LEN + 1), NOT_NULL), + + CEnd() +}; +} // namespace Show + +static const char* fts_config_key[] = { + FTS_OPTIMIZE_LIMIT_IN_SECS, + FTS_SYNCED_DOC_ID, + FTS_STOPWORD_TABLE_NAME, + FTS_USE_STOPWORD, + NULL +}; + +/*******************************************************************//** +Fill the dynamic table INFORMATION_SCHEMA.INNODB_FT_CONFIG +@return 0 on success, 1 on failure */ +static +int +i_s_fts_config_fill( +/*================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (ignored) */ +{ + Field** fields; + TABLE* table = (TABLE*) tables->table; + trx_t* trx; + fts_table_t fts_table; + dict_table_t* user_table; + ulint i = 0; + dict_index_t* index = NULL; + unsigned char str[FTS_MAX_CONFIG_VALUE_LEN + 1]; + + DBUG_ENTER("i_s_fts_config_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + MDL_ticket* mdl_ticket = nullptr; + user_table = dict_table_open_on_id( + innodb_ft_aux_table_id, false, DICT_TABLE_OP_NORMAL, + thd, &mdl_ticket); + + if (!user_table) { + DBUG_RETURN(0); + } + + if (!dict_table_has_fts_index(user_table)) { + dict_table_close(user_table, false, thd, mdl_ticket); + DBUG_RETURN(0); + } + + fields = table->field; + + trx = trx_create(); + trx->op_info = "Select for FTS CONFIG TABLE"; + + FTS_INIT_FTS_TABLE(&fts_table, "CONFIG", FTS_COMMON_TABLE, user_table); + + if (!ib_vector_is_empty(user_table->fts->indexes)) { + index = (dict_index_t*) ib_vector_getp_const( + user_table->fts->indexes, 0); + DBUG_ASSERT(!dict_index_is_online_ddl(index)); + } + + int ret = 0; + + while (fts_config_key[i]) { + fts_string_t value; + char* key_name; + ulint allocated = FALSE; + + value.f_len = FTS_MAX_CONFIG_VALUE_LEN; + + value.f_str = str; + + if (index + && strcmp(fts_config_key[i], FTS_TOTAL_WORD_COUNT) == 0) { + key_name = fts_config_create_index_param_name( + fts_config_key[i], index); + allocated = TRUE; + } else { + key_name = (char*) fts_config_key[i]; + } + + fts_config_get_value(trx, &fts_table, key_name, &value); + + if (allocated) { + ut_free(key_name); + } + + BREAK_IF(ret = field_store_string( + fields[FTS_CONFIG_KEY], fts_config_key[i])); + + BREAK_IF(ret = field_store_string( + fields[FTS_CONFIG_VALUE], + reinterpret_cast<const char*>(value.f_str))); + + BREAK_IF(ret = schema_table_store_record(thd, table)); + + i++; + } + + fts_sql_commit(trx); + + dict_table_close(user_table, false, thd, mdl_ticket); + + trx->free(); + + DBUG_RETURN(ret); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_FT_CONFIG +@return 0 on success */ +static +int +i_s_fts_config_init( +/*=================*/ + void* p) /*!< in/out: table schema object */ +{ + DBUG_ENTER("i_s_fts_config_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::i_s_fts_config_fields_info; + schema->fill_table = i_s_fts_config_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_ft_config = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_FT_CONFIG", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "INNODB AUXILIARY FTS CONFIG TABLE", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_fts_config_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/* Fields of the dynamic table INNODB_BUFFER_POOL_STATS. */ +static ST_FIELD_INFO i_s_innodb_buffer_stats_fields_info[]= +{ +#define IDX_BUF_STATS_POOL_ID 0 + Column("POOL_ID", ULong(), NOT_NULL), + +#define IDX_BUF_STATS_POOL_SIZE 1 + Column("POOL_SIZE", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_FREE_BUFFERS 2 + Column("FREE_BUFFERS", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_LRU_LEN 3 + Column("DATABASE_PAGES", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_OLD_LRU_LEN 4 + Column("OLD_DATABASE_PAGES", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_FLUSH_LIST_LEN 5 + Column("MODIFIED_DATABASE_PAGES", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_PENDING_ZIP 6 + Column("PENDING_DECOMPRESS", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_PENDING_READ 7 + Column("PENDING_READS",ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_FLUSH_LRU 8 + Column("PENDING_FLUSH_LRU",ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_FLUSH_LIST 9 + Column("PENDING_FLUSH_LIST", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_YOUNG 10 + Column("PAGES_MADE_YOUNG",ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_NOT_YOUNG 11 + Column("PAGES_NOT_MADE_YOUNG",ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_YOUNG_RATE 12 + Column("PAGES_MADE_YOUNG_RATE", Float(MAX_FLOAT_STR_LENGTH), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_NOT_YOUNG_RATE 13 + Column("PAGES_MADE_NOT_YOUNG_RATE", Float(MAX_FLOAT_STR_LENGTH), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_READ 14 + Column("NUMBER_PAGES_READ",ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_CREATED 15 + Column("NUMBER_PAGES_CREATED",ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_WRITTEN 16 + Column("NUMBER_PAGES_WRITTEN",ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_READ_RATE 17 + Column("PAGES_READ_RATE", Float(MAX_FLOAT_STR_LENGTH), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_CREATE_RATE 18 + Column("PAGES_CREATE_RATE", Float(MAX_FLOAT_STR_LENGTH), NOT_NULL), + +#define IDX_BUF_STATS_PAGE_WRITTEN_RATE 19 + Column("PAGES_WRITTEN_RATE",Float(MAX_FLOAT_STR_LENGTH), NOT_NULL), + +#define IDX_BUF_STATS_GET 20 + Column("NUMBER_PAGES_GET", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_HIT_RATE 21 + Column("HIT_RATE", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_MADE_YOUNG_PCT 22 + Column("YOUNG_MAKE_PER_THOUSAND_GETS", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_NOT_MADE_YOUNG_PCT 23 + Column("NOT_YOUNG_MAKE_PER_THOUSAND_GETS", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_READ_AHEAD 24 + Column("NUMBER_PAGES_READ_AHEAD", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_READ_AHEAD_EVICTED 25 + Column("NUMBER_READ_AHEAD_EVICTED", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_READ_AHEAD_RATE 26 + Column("READ_AHEAD_RATE", Float(MAX_FLOAT_STR_LENGTH), NOT_NULL), + +#define IDX_BUF_STATS_READ_AHEAD_EVICT_RATE 27 + Column("READ_AHEAD_EVICTED_RATE",Float(MAX_FLOAT_STR_LENGTH), NOT_NULL), + +#define IDX_BUF_STATS_LRU_IO_SUM 28 + Column("LRU_IO_TOTAL", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_LRU_IO_CUR 29 + Column("LRU_IO_CURRENT", ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_UNZIP_SUM 30 + Column("UNCOMPRESS_TOTAL",ULonglong(), NOT_NULL), + +#define IDX_BUF_STATS_UNZIP_CUR 31 + Column("UNCOMPRESS_CURRENT", ULonglong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/** Fill INFORMATION_SCHEMA.INNODB_BUFFER_POOL_STATS +@param[in,out] thd connection +@param[in,out] tables tables to fill +@return 0 on success, 1 on failure */ +static int i_s_innodb_stats_fill(THD *thd, TABLE_LIST * tables, Item *) +{ + TABLE* table; + Field** fields; + buf_pool_info_t info; + + DBUG_ENTER("i_s_innodb_stats_fill"); + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* Only allow the PROCESS privilege holder to access the stats */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + buf_stats_get_pool_info(&info); + + table = tables->table; + + fields = table->field; + + OK(fields[IDX_BUF_STATS_POOL_ID]->store(0, true)); + + OK(fields[IDX_BUF_STATS_POOL_SIZE]->store(info.pool_size, true)); + + OK(fields[IDX_BUF_STATS_LRU_LEN]->store(info.lru_len, true)); + + OK(fields[IDX_BUF_STATS_OLD_LRU_LEN]->store(info.old_lru_len, true)); + + OK(fields[IDX_BUF_STATS_FREE_BUFFERS]->store( + info.free_list_len, true)); + + OK(fields[IDX_BUF_STATS_FLUSH_LIST_LEN]->store( + info.flush_list_len, true)); + + OK(fields[IDX_BUF_STATS_PENDING_ZIP]->store(info.n_pend_unzip, true)); + + OK(fields[IDX_BUF_STATS_PENDING_READ]->store(info.n_pend_reads, true)); + + OK(fields[IDX_BUF_STATS_FLUSH_LRU]->store( + info.n_pending_flush_lru, true)); + + OK(fields[IDX_BUF_STATS_FLUSH_LIST]->store( + info.n_pending_flush_list, true)); + + OK(fields[IDX_BUF_STATS_PAGE_YOUNG]->store( + info.n_pages_made_young, true)); + + OK(fields[IDX_BUF_STATS_PAGE_NOT_YOUNG]->store( + info.n_pages_not_made_young, true)); + + OK(fields[IDX_BUF_STATS_PAGE_YOUNG_RATE]->store( + info.page_made_young_rate)); + + OK(fields[IDX_BUF_STATS_PAGE_NOT_YOUNG_RATE]->store( + info.page_not_made_young_rate)); + + OK(fields[IDX_BUF_STATS_PAGE_READ]->store(info.n_pages_read, true)); + + OK(fields[IDX_BUF_STATS_PAGE_CREATED]->store( + info.n_pages_created, true)); + + OK(fields[IDX_BUF_STATS_PAGE_WRITTEN]->store( + info.n_pages_written, true)); + + OK(fields[IDX_BUF_STATS_GET]->store(info.n_page_gets, true)); + + OK(fields[IDX_BUF_STATS_PAGE_READ_RATE]->store( + info.pages_read_rate)); + + OK(fields[IDX_BUF_STATS_PAGE_CREATE_RATE]->store( + info.pages_created_rate)); + + OK(fields[IDX_BUF_STATS_PAGE_WRITTEN_RATE]->store( + info.pages_written_rate)); + + if (info.n_page_get_delta) { + if (info.page_read_delta <= info.n_page_get_delta) { + OK(fields[IDX_BUF_STATS_HIT_RATE]->store( + static_cast<double>( + 1000 - (1000 * info.page_read_delta + / info.n_page_get_delta)))); + } else { + OK(fields[IDX_BUF_STATS_HIT_RATE]->store(0)); + } + + OK(fields[IDX_BUF_STATS_MADE_YOUNG_PCT]->store( + 1000 * info.young_making_delta + / info.n_page_get_delta, true)); + + OK(fields[IDX_BUF_STATS_NOT_MADE_YOUNG_PCT]->store( + 1000 * info.not_young_making_delta + / info.n_page_get_delta, true)); + } else { + OK(fields[IDX_BUF_STATS_HIT_RATE]->store(0, true)); + OK(fields[IDX_BUF_STATS_MADE_YOUNG_PCT]->store(0, true)); + OK(fields[IDX_BUF_STATS_NOT_MADE_YOUNG_PCT]->store(0, true)); + } + + OK(fields[IDX_BUF_STATS_READ_AHEAD]->store( + info.n_ra_pages_read, true)); + + OK(fields[IDX_BUF_STATS_READ_AHEAD_EVICTED]->store( + info.n_ra_pages_evicted, true)); + + OK(fields[IDX_BUF_STATS_READ_AHEAD_RATE]->store( + info.pages_readahead_rate)); + + OK(fields[IDX_BUF_STATS_READ_AHEAD_EVICT_RATE]->store( + info.pages_evicted_rate)); + + OK(fields[IDX_BUF_STATS_LRU_IO_SUM]->store(info.io_sum, true)); + + OK(fields[IDX_BUF_STATS_LRU_IO_CUR]->store(info.io_cur, true)); + + OK(fields[IDX_BUF_STATS_UNZIP_SUM]->store(info.unzip_sum, true)); + + OK(fields[IDX_BUF_STATS_UNZIP_CUR]->store(info.unzip_cur, true)); + + DBUG_RETURN(schema_table_store_record(thd, table)); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_BUFFER_POOL_STATS. +@return 0 on success, 1 on failure */ +static +int +i_s_innodb_buffer_pool_stats_init( +/*==============================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("i_s_innodb_buffer_pool_stats_init"); + + schema = reinterpret_cast<ST_SCHEMA_TABLE*>(p); + + schema->fields_info = Show::i_s_innodb_buffer_stats_fields_info; + schema->fill_table = i_s_innodb_stats_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_buffer_stats = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_BUFFER_POOL_STATS", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB Buffer Pool Statistics Information ", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_innodb_buffer_pool_stats_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +/** These must correspond to the first values of buf_page_state */ +static const LEX_CSTRING page_state_values[] = +{ + { STRING_WITH_LEN("NOT_USED") }, + { STRING_WITH_LEN("MEMORY") }, + { STRING_WITH_LEN("REMOVE_HASH") }, + { STRING_WITH_LEN("FILE_PAGE") }, +}; + +static const TypelibBuffer<4> page_state_values_typelib(page_state_values); + +static const LEX_CSTRING io_values[] = +{ + { STRING_WITH_LEN("IO_NONE") }, + { STRING_WITH_LEN("IO_READ") }, + { STRING_WITH_LEN("IO_WRITE") } +}; + + +static TypelibBuffer<3> io_values_typelib(io_values); + +namespace Show { +/* Fields of the dynamic table INNODB_BUFFER_POOL_PAGE. */ +static ST_FIELD_INFO i_s_innodb_buffer_page_fields_info[]= +{ +#define IDX_BUFFER_POOL_ID 0 + Column("POOL_ID", ULong(), NOT_NULL), + +#define IDX_BUFFER_BLOCK_ID 1 + Column("BLOCK_ID", ULonglong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_SPACE 2 + Column("SPACE", ULong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_NUM 3 + Column("PAGE_NUMBER", ULong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_TYPE 4 + Column("PAGE_TYPE", Varchar(64), NULLABLE), + +#define IDX_BUFFER_PAGE_FLUSH_TYPE 5 + Column("FLUSH_TYPE", ULong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_FIX_COUNT 6 + Column("FIX_COUNT", ULong(), NOT_NULL), + +#ifdef BTR_CUR_HASH_ADAPT +#define IDX_BUFFER_PAGE_HASHED 7 + Column("IS_HASHED", SLong(1), NOT_NULL), +#endif /* BTR_CUR_HASH_ADAPT */ +#define IDX_BUFFER_PAGE_NEWEST_MOD 7 + I_S_AHI + Column("NEWEST_MODIFICATION", ULonglong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_OLDEST_MOD 8 + I_S_AHI + Column("OLDEST_MODIFICATION", ULonglong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_ACCESS_TIME 9 + I_S_AHI + Column("ACCESS_TIME", ULonglong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_TABLE_NAME 10 + I_S_AHI + Column("TABLE_NAME", Varchar(1024), NULLABLE), + +#define IDX_BUFFER_PAGE_INDEX_NAME 11 + I_S_AHI + Column("INDEX_NAME", Varchar(NAME_CHAR_LEN), NULLABLE), + +#define IDX_BUFFER_PAGE_NUM_RECS 12 + I_S_AHI + Column("NUMBER_RECORDS", ULonglong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_DATA_SIZE 13 + I_S_AHI + Column("DATA_SIZE", ULonglong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_ZIP_SIZE 14 + I_S_AHI + Column("COMPRESSED_SIZE", ULonglong(), NOT_NULL), + +#define IDX_BUFFER_PAGE_STATE 15 + I_S_AHI + Column("PAGE_STATE", Enum(&page_state_values_typelib), NOT_NULL), + +#define IDX_BUFFER_PAGE_IO_FIX 16 + I_S_AHI + Column("IO_FIX", Enum(&io_values_typelib), NOT_NULL), + +#define IDX_BUFFER_PAGE_IS_OLD 17 + I_S_AHI + Column("IS_OLD", SLong(1), NOT_NULL), + +#define IDX_BUFFER_PAGE_FREE_CLOCK 18 + I_S_AHI + Column("FREE_PAGE_CLOCK", ULonglong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/*******************************************************************//** +Fill Information Schema table INNODB_BUFFER_PAGE with information +cached in the buf_page_info_t array +@return 0 on success, 1 on failure */ +static +int +i_s_innodb_buffer_page_fill( +/*========================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + const buf_page_info_t* info_array, /*!< in: array cached page + info */ + ulint num_page) /*!< in: number of page info + cached */ +{ + TABLE* table; + Field** fields; + + compile_time_assert(I_S_PAGE_TYPE_LAST < 1 << I_S_PAGE_TYPE_BITS); + + DBUG_ENTER("i_s_innodb_buffer_page_fill"); + + table = tables->table; + + fields = table->field; + + /* Iterate through the cached array and fill the I_S table rows */ + for (ulint i = 0; i < num_page; i++) { + const buf_page_info_t* page_info; + char table_name[MAX_FULL_NAME_LEN + 1]; + const char* table_name_end = NULL; + + page_info = info_array + i; + + OK(fields[IDX_BUFFER_POOL_ID]->store(0, true)); + + OK(fields[IDX_BUFFER_BLOCK_ID]->store( + page_info->block_id, true)); + + OK(fields[IDX_BUFFER_PAGE_SPACE]->store( + page_info->id.space(), true)); + + OK(fields[IDX_BUFFER_PAGE_NUM]->store( + page_info->id.page_no(), true)); + + OK(field_store_string( + fields[IDX_BUFFER_PAGE_TYPE], + i_s_page_type[page_info->page_type].type_str)); + + OK(fields[IDX_BUFFER_PAGE_FLUSH_TYPE]->store(0, true)); + + OK(fields[IDX_BUFFER_PAGE_FIX_COUNT]->store( + ~buf_page_t::LRU_MASK & page_info->state, true)); + +#ifdef BTR_CUR_HASH_ADAPT + OK(fields[IDX_BUFFER_PAGE_HASHED]->store( + page_info->hashed, true)); +#endif /* BTR_CUR_HASH_ADAPT */ + + OK(fields[IDX_BUFFER_PAGE_NEWEST_MOD]->store( + page_info->newest_mod, true)); + + OK(fields[IDX_BUFFER_PAGE_OLDEST_MOD]->store( + page_info->oldest_mod, true)); + + OK(fields[IDX_BUFFER_PAGE_ACCESS_TIME]->store( + page_info->access_time, true)); + + fields[IDX_BUFFER_PAGE_TABLE_NAME]->set_null(); + + fields[IDX_BUFFER_PAGE_INDEX_NAME]->set_null(); + + /* If this is an index page, fetch the index name + and table name */ + if (page_info->page_type == I_S_PAGE_TYPE_INDEX) { + bool ret = false; + + dict_sys.freeze(SRW_LOCK_CALL); + + const dict_index_t* index = + dict_index_get_if_in_cache_low( + page_info->index_id); + + if (index) { + table_name_end = innobase_convert_name( + table_name, sizeof(table_name), + index->table->name.m_name, + strlen(index->table->name.m_name), + thd); + + ret = fields[IDX_BUFFER_PAGE_TABLE_NAME] + ->store(table_name, + static_cast<uint>( + table_name_end + - table_name), + system_charset_info) + || fields[IDX_BUFFER_PAGE_INDEX_NAME] + ->store(index->name, + uint(strlen(index->name)), + system_charset_info); + } + + dict_sys.unfreeze(); + + OK(ret); + + if (index) { + fields[IDX_BUFFER_PAGE_TABLE_NAME] + ->set_notnull(); + fields[IDX_BUFFER_PAGE_INDEX_NAME] + ->set_notnull(); + } + } + + OK(fields[IDX_BUFFER_PAGE_NUM_RECS]->store( + page_info->num_recs, true)); + + OK(fields[IDX_BUFFER_PAGE_DATA_SIZE]->store( + page_info->data_size, true)); + + OK(fields[IDX_BUFFER_PAGE_ZIP_SIZE]->store( + page_info->zip_ssize + ? (UNIV_ZIP_SIZE_MIN >> 1) << page_info->zip_ssize + : 0, true)); + + static_assert(buf_page_t::NOT_USED == 0, "compatibility"); + static_assert(buf_page_t::MEMORY == 1, "compatibility"); + static_assert(buf_page_t::REMOVE_HASH == 2, "compatibility"); + + OK(fields[IDX_BUFFER_PAGE_STATE]->store( + std::min<uint32_t>(3, page_info->state) + 1, true)); + + static_assert(buf_page_t::UNFIXED == 1U << 29, "comp."); + static_assert(buf_page_t::READ_FIX == 4U << 29, "comp."); + static_assert(buf_page_t::WRITE_FIX == 5U << 29, "comp."); + + unsigned io_fix = page_info->state >> 29; + if (io_fix < 4) { + io_fix = 1; + } else if (io_fix > 5) { + io_fix = 3; + } else { + io_fix -= 2; + } + + OK(fields[IDX_BUFFER_PAGE_IO_FIX]->store(io_fix, true)); + + OK(fields[IDX_BUFFER_PAGE_IS_OLD]->store( + page_info->is_old, true)); + + OK(fields[IDX_BUFFER_PAGE_FREE_CLOCK]->store( + page_info->freed_page_clock, true)); + + OK(schema_table_store_record(thd, table)); + } + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Set appropriate page type to a buf_page_info_t structure */ +static +void +i_s_innodb_set_page_type( +/*=====================*/ + buf_page_info_t*page_info, /*!< in/out: structure to fill with + scanned info */ + const byte* frame) /*!< in: buffer frame */ +{ + uint16_t page_type = fil_page_get_type(frame); + + if (fil_page_type_is_index(page_type)) { + const page_t* page = (const page_t*) frame; + + page_info->index_id = btr_page_get_index_id(page); + + /* FIL_PAGE_INDEX and FIL_PAGE_RTREE are a bit special, + their values are defined as 17855 and 17854, so we cannot + use them to index into i_s_page_type[] array, its array index + in the i_s_page_type[] array is I_S_PAGE_TYPE_INDEX + (1) for index pages or I_S_PAGE_TYPE_IBUF for + change buffer index pages */ + if (page_type == FIL_PAGE_RTREE) { + page_info->page_type = I_S_PAGE_TYPE_RTREE; + } else if (page_info->index_id + == static_cast<index_id_t>(DICT_IBUF_ID_MIN + + IBUF_SPACE_ID)) { + page_info->page_type = I_S_PAGE_TYPE_IBUF; + } else { + ut_ad(page_type == FIL_PAGE_INDEX + || page_type == FIL_PAGE_TYPE_INSTANT); + page_info->page_type = I_S_PAGE_TYPE_INDEX; + } + + page_info->data_size = uint16_t(page_header_get_field( + page, PAGE_HEAP_TOP) - (page_is_comp(page) + ? PAGE_NEW_SUPREMUM_END + : PAGE_OLD_SUPREMUM_END) + - page_header_get_field(page, PAGE_GARBAGE)); + + page_info->num_recs = page_get_n_recs(page) & ((1U << 14) - 1); + } else if (page_type > FIL_PAGE_TYPE_LAST) { + /* Encountered an unknown page type */ + page_info->page_type = I_S_PAGE_TYPE_UNKNOWN; + } else { + /* Make sure we get the right index into the + i_s_page_type[] array */ + ut_a(page_type == i_s_page_type[page_type].type_value); + + page_info->page_type = page_type & 0xf; + } +} +/*******************************************************************//** +Scans pages in the buffer cache, and collect their general information +into the buf_page_info_t array which is zero-filled. So any fields +that are not initialized in the function will default to 0 */ +static +void +i_s_innodb_buffer_page_get_info( +/*============================*/ + const buf_page_t*bpage, /*!< in: buffer pool page to scan */ + ulint pos, /*!< in: buffer block position in + buffer pool or in the LRU list */ + buf_page_info_t*page_info) /*!< in: zero filled info structure; + out: structure filled with scanned + info */ +{ + page_info->block_id = pos; + + static_assert(buf_page_t::NOT_USED == 0, "compatibility"); + static_assert(buf_page_t::MEMORY == 1, "compatibility"); + static_assert(buf_page_t::REMOVE_HASH == 2, "compatibility"); + static_assert(buf_page_t::UNFIXED == 1U << 29, "compatibility"); + static_assert(buf_page_t::READ_FIX == 4U << 29, "compatibility"); + static_assert(buf_page_t::WRITE_FIX == 5U << 29, "compatibility"); + + page_info->state = bpage->state(); + + if (page_info->state < buf_page_t::UNFIXED) { + page_info->page_type = I_S_PAGE_TYPE_UNKNOWN; + page_info->compressed_only = false; + } else { + const byte* frame; + + page_info->id = bpage->id(); + + page_info->oldest_mod = bpage->oldest_modification(); + + page_info->access_time = bpage->access_time; + + page_info->zip_ssize = bpage->zip.ssize; + + page_info->is_old = bpage->old; + + page_info->freed_page_clock = bpage->freed_page_clock; + + if (page_info->state >= buf_page_t::READ_FIX + && page_info->state < buf_page_t::WRITE_FIX) { + page_info->page_type = I_S_PAGE_TYPE_UNKNOWN; + page_info->newest_mod = 0; + return; + } + + page_info->compressed_only = !bpage->frame, + frame = bpage->frame; + if (UNIV_LIKELY(frame != nullptr)) { +#ifdef BTR_CUR_HASH_ADAPT + /* Note: this may be a false positive, that + is, block->index will not always be set to + NULL when the last adaptive hash index + reference is dropped. */ + page_info->hashed = + reinterpret_cast<const buf_block_t*>(bpage) + ->index != nullptr; +#endif /* BTR_CUR_HASH_ADAPT */ + } else { + ut_ad(page_info->zip_ssize); + frame = bpage->zip.data; + } + + page_info->newest_mod = mach_read_from_8(FIL_PAGE_LSN + frame); + i_s_innodb_set_page_type(page_info, frame); + } +} + +/*******************************************************************//** +This is the function that goes through each block of the buffer pool +and fetch information to information schema tables: INNODB_BUFFER_PAGE. +@param[in,out] thd connection +@param[in,out] tables tables to fill +@return 0 on success, 1 on failure */ +static int i_s_innodb_buffer_page_fill(THD *thd, TABLE_LIST *tables, Item *) +{ + int status = 0; + mem_heap_t* heap; + + DBUG_ENTER("i_s_innodb_buffer_page_fill"); + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS privilege */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + heap = mem_heap_create(10000); + + for (ulint n = 0; + n < ut_min(buf_pool.n_chunks, buf_pool.n_chunks_new); n++) { + const buf_block_t* block; + ulint n_blocks; + buf_page_info_t* info_buffer; + ulint num_page; + ulint mem_size; + ulint chunk_size; + ulint num_to_process = 0; + ulint block_id = 0; + + /* Get buffer block of the nth chunk */ + block = buf_pool.chunks[n].blocks; + chunk_size = buf_pool.chunks[n].size; + num_page = 0; + + while (chunk_size > 0) { + /* we cache maximum MAX_BUF_INFO_CACHED number of + buffer page info */ + num_to_process = ut_min(chunk_size, + (ulint)MAX_BUF_INFO_CACHED); + + mem_size = num_to_process * sizeof(buf_page_info_t); + + /* For each chunk, we'll pre-allocate information + structures to cache the page information read from + the buffer pool. Doing so before obtain any mutex */ + info_buffer = (buf_page_info_t*) mem_heap_zalloc( + heap, mem_size); + + /* Obtain appropriate mutexes. Since this is diagnostic + buffer pool info printout, we are not required to + preserve the overall consistency, so we can + release mutex periodically */ + mysql_mutex_lock(&buf_pool.mutex); + + /* GO through each block in the chunk */ + for (n_blocks = num_to_process; n_blocks--; block++) { + i_s_innodb_buffer_page_get_info( + &block->page, block_id, + info_buffer + num_page); + block_id++; + num_page++; + } + + mysql_mutex_unlock(&buf_pool.mutex); + + /* Fill in information schema table with information + just collected from the buffer chunk scan */ + status = i_s_innodb_buffer_page_fill( + thd, tables, info_buffer, + num_page); + + /* If something goes wrong, break and return */ + if (status) { + break; + } + + mem_heap_empty(heap); + chunk_size -= num_to_process; + num_page = 0; + } + } + + mem_heap_free(heap); + + DBUG_RETURN(status); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_BUFFER_PAGE. +@return 0 on success, 1 on failure */ +static +int +i_s_innodb_buffer_page_init( +/*========================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("i_s_innodb_buffer_page_init"); + + schema = reinterpret_cast<ST_SCHEMA_TABLE*>(p); + + schema->fields_info = Show::i_s_innodb_buffer_page_fields_info; + schema->fill_table = i_s_innodb_buffer_page_fill; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_buffer_page = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_BUFFER_PAGE", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB Buffer Page Information", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_innodb_buffer_page_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +static ST_FIELD_INFO i_s_innodb_buf_page_lru_fields_info[] = +{ +#define IDX_BUF_LRU_POOL_ID 0 + Column("POOL_ID", ULong(), NOT_NULL), + +#define IDX_BUF_LRU_POS 1 + Column("LRU_POSITION", ULonglong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_SPACE 2 + Column("SPACE", ULong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_NUM 3 + Column("PAGE_NUMBER", ULong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_TYPE 4 + Column("PAGE_TYPE", Varchar(64), NULLABLE), + +#define IDX_BUF_LRU_PAGE_FLUSH_TYPE 5 + Column("FLUSH_TYPE", ULong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_FIX_COUNT 6 + Column("FIX_COUNT", ULong(), NOT_NULL), + +#ifdef BTR_CUR_HASH_ADAPT +#define IDX_BUF_LRU_PAGE_HASHED 7 + Column("IS_HASHED", SLong(1), NOT_NULL), +#endif /* BTR_CUR_HASH_ADAPT */ +#define IDX_BUF_LRU_PAGE_NEWEST_MOD 7 + I_S_AHI + Column("NEWEST_MODIFICATION",ULonglong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_OLDEST_MOD 8 + I_S_AHI + Column("OLDEST_MODIFICATION",ULonglong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_ACCESS_TIME 9 + I_S_AHI + Column("ACCESS_TIME",ULonglong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_TABLE_NAME 10 + I_S_AHI + Column("TABLE_NAME", Varchar(1024), NULLABLE), + +#define IDX_BUF_LRU_PAGE_INDEX_NAME 11 + I_S_AHI + Column("INDEX_NAME", Varchar(NAME_CHAR_LEN), NULLABLE), + +#define IDX_BUF_LRU_PAGE_NUM_RECS 12 + I_S_AHI + Column("NUMBER_RECORDS", ULonglong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_DATA_SIZE 13 + I_S_AHI + Column("DATA_SIZE", ULonglong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_ZIP_SIZE 14 + I_S_AHI + Column("COMPRESSED_SIZE",ULonglong(), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_STATE 15 + I_S_AHI + Column("COMPRESSED", SLong(1), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_IO_FIX 16 + I_S_AHI + Column("IO_FIX", Enum(&io_values_typelib), NOT_NULL), + +#define IDX_BUF_LRU_PAGE_IS_OLD 17 + I_S_AHI + Column("IS_OLD", SLong(1), NULLABLE), + +#define IDX_BUF_LRU_PAGE_FREE_CLOCK 18 + I_S_AHI + Column("FREE_PAGE_CLOCK", ULonglong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/*******************************************************************//** +Fill Information Schema table INNODB_BUFFER_PAGE_LRU with information +cached in the buf_page_info_t array +@return 0 on success, 1 on failure */ +static +int +i_s_innodb_buf_page_lru_fill( +/*=========================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + const buf_page_info_t* info_array, /*!< in: array cached page + info */ + ulint num_page) /*!< in: number of page info + cached */ +{ + DBUG_ENTER("i_s_innodb_buf_page_lru_fill"); + + TABLE* table = tables->table; + Field** fields = table->field; + + /* Iterate through the cached array and fill the I_S table rows */ + for (ulint i = 0; i < num_page; i++) { + const buf_page_info_t* page_info; + char table_name[MAX_FULL_NAME_LEN + 1]; + const char* table_name_end = NULL; + + page_info = info_array + i; + + OK(fields[IDX_BUF_LRU_POOL_ID]->store(0, true)); + + OK(fields[IDX_BUF_LRU_POS]->store( + page_info->block_id, true)); + + OK(fields[IDX_BUF_LRU_PAGE_SPACE]->store( + page_info->id.space(), true)); + + OK(fields[IDX_BUF_LRU_PAGE_NUM]->store( + page_info->id.page_no(), true)); + + OK(field_store_string( + fields[IDX_BUF_LRU_PAGE_TYPE], + i_s_page_type[page_info->page_type].type_str)); + + OK(fields[IDX_BUF_LRU_PAGE_FLUSH_TYPE]->store(0, true)); + + OK(fields[IDX_BUF_LRU_PAGE_FIX_COUNT]->store( + ~buf_page_t::LRU_MASK & page_info->state, true)); + +#ifdef BTR_CUR_HASH_ADAPT + OK(fields[IDX_BUF_LRU_PAGE_HASHED]->store( + page_info->hashed, true)); +#endif /* BTR_CUR_HASH_ADAPT */ + + OK(fields[IDX_BUF_LRU_PAGE_NEWEST_MOD]->store( + page_info->newest_mod, true)); + + OK(fields[IDX_BUF_LRU_PAGE_OLDEST_MOD]->store( + page_info->oldest_mod, true)); + + OK(fields[IDX_BUF_LRU_PAGE_ACCESS_TIME]->store( + page_info->access_time, true)); + + fields[IDX_BUF_LRU_PAGE_TABLE_NAME]->set_null(); + + fields[IDX_BUF_LRU_PAGE_INDEX_NAME]->set_null(); + + /* If this is an index page, fetch the index name + and table name */ + if (page_info->page_type == I_S_PAGE_TYPE_INDEX) { + bool ret = false; + + dict_sys.freeze(SRW_LOCK_CALL); + + const dict_index_t* index = + dict_index_get_if_in_cache_low( + page_info->index_id); + + if (index) { + table_name_end = innobase_convert_name( + table_name, sizeof(table_name), + index->table->name.m_name, + strlen(index->table->name.m_name), + thd); + + ret = fields[IDX_BUF_LRU_PAGE_TABLE_NAME] + ->store(table_name, + static_cast<uint>( + table_name_end + - table_name), + system_charset_info) + || fields[IDX_BUF_LRU_PAGE_INDEX_NAME] + ->store(index->name, + uint(strlen(index->name)), + system_charset_info); + } + + dict_sys.unfreeze(); + + OK(ret); + + if (index) { + fields[IDX_BUF_LRU_PAGE_TABLE_NAME] + ->set_notnull(); + fields[IDX_BUF_LRU_PAGE_INDEX_NAME] + ->set_notnull(); + } + } + + OK(fields[IDX_BUF_LRU_PAGE_NUM_RECS]->store( + page_info->num_recs, true)); + + OK(fields[IDX_BUF_LRU_PAGE_DATA_SIZE]->store( + page_info->data_size, true)); + + OK(fields[IDX_BUF_LRU_PAGE_ZIP_SIZE]->store( + page_info->zip_ssize + ? 512 << page_info->zip_ssize : 0, true)); + + OK(fields[IDX_BUF_LRU_PAGE_STATE]->store( + page_info->compressed_only, true)); + + static_assert(buf_page_t::UNFIXED == 1U << 29, "comp."); + static_assert(buf_page_t::READ_FIX == 4U << 29, "comp."); + static_assert(buf_page_t::WRITE_FIX == 5U << 29, "comp."); + + unsigned io_fix = page_info->state >> 29; + if (io_fix < 4) { + io_fix = 1; + } else if (io_fix > 5) { + io_fix = 3; + } else { + io_fix -= 2; + } + + OK(fields[IDX_BUF_LRU_PAGE_IO_FIX]->store(io_fix, true)); + + OK(fields[IDX_BUF_LRU_PAGE_IS_OLD]->store( + page_info->is_old, true)); + + OK(fields[IDX_BUF_LRU_PAGE_FREE_CLOCK]->store( + page_info->freed_page_clock, true)); + + OK(schema_table_store_record(thd, table)); + } + + DBUG_RETURN(0); +} + +/** Fill the table INFORMATION_SCHEMA.INNODB_BUFFER_PAGE_LRU. +@param[in] thd thread +@param[in,out] tables tables to fill +@return 0 on success, 1 on failure */ +static int i_s_innodb_fill_buffer_lru(THD *thd, TABLE_LIST *tables, Item *) +{ + int status = 0; + buf_page_info_t* info_buffer; + ulint lru_pos = 0; + const buf_page_t* bpage; + ulint lru_len; + + DBUG_ENTER("i_s_innodb_fill_buffer_lru"); + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to any users that do not hold PROCESS_ACL */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + /* Aquire the mutex before allocating info_buffer, since + UT_LIST_GET_LEN(buf_pool.LRU) could change */ + mysql_mutex_lock(&buf_pool.mutex); + + lru_len = UT_LIST_GET_LEN(buf_pool.LRU); + + /* Print error message if malloc fail */ + info_buffer = (buf_page_info_t*) my_malloc(PSI_INSTRUMENT_ME, + lru_len * sizeof *info_buffer, MYF(MY_WME | MY_ZEROFILL)); + + if (!info_buffer) { + status = 1; + goto exit; + } + + /* Walk through Pool's LRU list and print the buffer page + information */ + bpage = UT_LIST_GET_LAST(buf_pool.LRU); + + while (bpage != NULL) { + /* Use the same function that collect buffer info for + INNODB_BUFFER_PAGE to get buffer page info */ + i_s_innodb_buffer_page_get_info(bpage, lru_pos, + (info_buffer + lru_pos)); + + bpage = UT_LIST_GET_PREV(LRU, bpage); + + lru_pos++; + } + + ut_ad(lru_pos == lru_len); + ut_ad(lru_pos == UT_LIST_GET_LEN(buf_pool.LRU)); + +exit: + mysql_mutex_unlock(&buf_pool.mutex); + + if (info_buffer) { + status = i_s_innodb_buf_page_lru_fill( + thd, tables, info_buffer, lru_len); + + my_free(info_buffer); + } + + DBUG_RETURN(status); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_BUFFER_PAGE_LRU. +@return 0 on success, 1 on failure */ +static +int +i_s_innodb_buffer_page_lru_init( +/*============================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("i_s_innodb_buffer_page_lru_init"); + + schema = reinterpret_cast<ST_SCHEMA_TABLE*>(p); + + schema->fields_info = Show::i_s_innodb_buf_page_lru_fields_info; + schema->fill_table = i_s_innodb_fill_buffer_lru; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_buffer_page_lru = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_BUFFER_PAGE_LRU", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB Buffer Page in LRU", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + i_s_innodb_buffer_page_lru_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +/*******************************************************************//** +Unbind a dynamic INFORMATION_SCHEMA table. +@return 0 */ +static int i_s_common_deinit(void*) +{ + DBUG_ENTER("i_s_common_deinit"); + + /* Do nothing */ + + DBUG_RETURN(0); +} + +static const LEX_CSTRING row_format_values[] = +{ + { STRING_WITH_LEN("Redundant") }, + { STRING_WITH_LEN("Compact") }, + { STRING_WITH_LEN("Compressed") }, + { STRING_WITH_LEN("Dynamic") } +}; + +static TypelibBuffer<4> row_format_values_typelib(row_format_values); + +static const LEX_CSTRING space_type_values[] = +{ + { STRING_WITH_LEN("Single") }, + { STRING_WITH_LEN("System") } +}; + +static TypelibBuffer<2> space_type_values_typelib(space_type_values); + +namespace Show { +/** SYS_TABLES ***************************************************/ +/* Fields of the dynamic table INFORMATION_SCHEMA.SYS_TABLES */ +static ST_FIELD_INFO innodb_sys_tables_fields_info[]= +{ +#define SYS_TABLES_ID 0 + Column("TABLE_ID", ULonglong(), NOT_NULL), + +#define SYS_TABLES_NAME 1 + Column("NAME", Varchar(MAX_FULL_NAME_LEN + 1), NOT_NULL), + +#define SYS_TABLES_FLAG 2 + Column("FLAG", SLong(), NOT_NULL), + +#define SYS_TABLES_NUM_COLUMN 3 + Column("N_COLS", ULong(), NOT_NULL), + +#define SYS_TABLES_SPACE 4 + Column("SPACE", ULong(), NOT_NULL), + +#define SYS_TABLES_ROW_FORMAT 5 + Column("ROW_FORMAT", Enum(&row_format_values_typelib), NULLABLE), + +#define SYS_TABLES_ZIP_PAGE_SIZE 6 + Column("ZIP_PAGE_SIZE", ULong(), NOT_NULL), + +#define SYS_TABLES_SPACE_TYPE 7 + Column("SPACE_TYPE", Enum(&space_type_values_typelib), NULLABLE), + + CEnd() +}; +} // namespace Show + +/**********************************************************************//** +Populate information_schema.innodb_sys_tables table with information +from SYS_TABLES. +@return 0 on success */ +static +int +i_s_dict_fill_sys_tables( +/*=====================*/ + THD* thd, /*!< in: thread */ + dict_table_t* table, /*!< in: table */ + TABLE* table_to_fill) /*!< in/out: fill this table */ +{ + Field** fields; + ulint compact = DICT_TF_GET_COMPACT(table->flags); + ulint atomic_blobs = DICT_TF_HAS_ATOMIC_BLOBS( + table->flags); + const ulint zip_size = dict_tf_get_zip_size(table->flags); + const char* row_format; + + if (!compact) { + row_format = "Redundant"; + } else if (!atomic_blobs) { + row_format = "Compact"; + } else if (DICT_TF_GET_ZIP_SSIZE(table->flags)) { + row_format = "Compressed"; + } else { + row_format = "Dynamic"; + } + + DBUG_ENTER("i_s_dict_fill_sys_tables"); + + fields = table_to_fill->field; + + OK(fields[SYS_TABLES_ID]->store(longlong(table->id), TRUE)); + + OK(field_store_string(fields[SYS_TABLES_NAME], table->name.m_name)); + + OK(fields[SYS_TABLES_FLAG]->store(table->flags)); + + OK(fields[SYS_TABLES_NUM_COLUMN]->store(table->n_cols)); + + OK(fields[SYS_TABLES_SPACE]->store(table->space_id, true)); + + OK(field_store_string(fields[SYS_TABLES_ROW_FORMAT], row_format)); + + OK(fields[SYS_TABLES_ZIP_PAGE_SIZE]->store(zip_size, true)); + + OK(field_store_string(fields[SYS_TABLES_SPACE_TYPE], + table->space_id ? "Single" : "System")); + + OK(schema_table_store_record(thd, table_to_fill)); + + DBUG_RETURN(0); +} + +/** Convert one SYS_TABLES record to dict_table_t. +@param pcur persistent cursor position on SYS_TABLES record +@param mtr mini-transaction (nullptr=use the dict_sys cache) +@param rec record to read from (nullptr=use the dict_sys cache) +@param table the converted dict_table_t +@return error message +@retval nullptr on success */ +static const char *i_s_sys_tables_rec(const btr_pcur_t &pcur, mtr_t *mtr, + const rec_t *rec, dict_table_t **table) +{ + static_assert(DICT_FLD__SYS_TABLES__NAME == 0, "compatibility"); + size_t len; + if (rec_get_1byte_offs_flag(pcur.old_rec)) + { + len= rec_1_get_field_end_info(pcur.old_rec, 0); + if (len & REC_1BYTE_SQL_NULL_MASK) + return "corrupted SYS_TABLES.NAME"; + } + else + { + len= rec_2_get_field_end_info(pcur.old_rec, 0); + static_assert(REC_2BYTE_EXTERN_MASK == 16384, "compatibility"); + if (len >= REC_2BYTE_EXTERN_MASK) + return "corrupted SYS_TABLES.NAME"; + } + + if (rec) + return dict_load_table_low(mtr, false, rec, table); + + *table= dict_sys.load_table + (span<const char>{reinterpret_cast<const char*>(pcur.old_rec), len}); + return *table ? nullptr : "Table not found in cache"; +} + +/*******************************************************************//** +Function to go through each record in SYS_TABLES table, and fill the +information_schema.innodb_sys_tables table with related table information +@return 0 on success */ +static +int +i_s_sys_tables_fill_table( +/*======================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + btr_pcur_t pcur; + mtr_t mtr; + + DBUG_ENTER("i_s_sys_tables_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + + for (const rec_t *rec = dict_startscan_system(&pcur, &mtr, + dict_sys.sys_tables); + rec; rec = dict_getnext_system(&pcur, &mtr)) { + if (rec_get_deleted_flag(rec, 0)) { + continue; + } + + const char* err_msg; + dict_table_t* table_rec; + + /* Create and populate a dict_table_t structure with + information from SYS_TABLES row */ + err_msg = i_s_sys_tables_rec(pcur, &mtr, rec, &table_rec); + mtr.commit(); + dict_sys.unlock(); + + if (!err_msg) { + i_s_dict_fill_sys_tables(thd, table_rec, + tables->table); + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_FIND_SYSTEM_REC, "%s", + err_msg); + } + + if (table_rec) { + dict_mem_table_free(table_rec); + } + + /* Get the next record */ + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + } + + mtr.commit(); + dict_sys.unlock(); + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_sys_tables +@return 0 on success */ +static +int +innodb_sys_tables_init( +/*===================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_tables_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sys_tables_fields_info; + schema->fill_table = i_s_sys_tables_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_tables = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_TABLES", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB SYS_TABLES", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_tables_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/** SYS_TABLESTATS ***********************************************/ +/* Fields of the dynamic table INFORMATION_SCHEMA.SYS_TABLESTATS */ +static ST_FIELD_INFO innodb_sys_tablestats_fields_info[]= +{ +#define SYS_TABLESTATS_ID 0 + Column("TABLE_ID", ULonglong(), NOT_NULL), + +#define SYS_TABLESTATS_NAME 1 + Column("NAME", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define SYS_TABLESTATS_INIT 2 + Column("STATS_INITIALIZED", SLong(1), NOT_NULL), + +#define SYS_TABLESTATS_NROW 3 + Column("NUM_ROWS", ULonglong(), NOT_NULL), + +#define SYS_TABLESTATS_CLUST_SIZE 4 + Column("CLUST_INDEX_SIZE", ULonglong(), NOT_NULL), + +#define SYS_TABLESTATS_INDEX_SIZE 5 + Column("OTHER_INDEX_SIZE", ULonglong(), NOT_NULL), + +#define SYS_TABLESTATS_MODIFIED 6 + Column("MODIFIED_COUNTER", ULonglong(), NOT_NULL), + +#define SYS_TABLESTATS_AUTONINC 7 + Column("AUTOINC", ULonglong(), NOT_NULL), + +#define SYS_TABLESTATS_TABLE_REF_COUNT 8 + Column("REF_COUNT", SLong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/** Populate information_schema.innodb_sys_tablestats table with a table, +and release exclusive dict_sys.latch. +@param[in] thd connection +@param[in,out] table InnoDB table metadata +@param[in,out] table_to_fill INFORMATION_SCHEMA.INNODB_SYS_TABLESTATS +@return 0 on success */ +static +int +i_s_dict_fill_sys_tablestats(THD* thd, dict_table_t *table, + TABLE* table_to_fill) +{ + DBUG_ENTER("i_s_dict_fill_sys_tablestats"); + + Field **fields= table_to_fill->field; + + { + table->stats_mutex_lock(); + auto _ = make_scope_exit([table]() { + table->stats_mutex_unlock(); dict_sys.unlock(); }); + + OK(fields[SYS_TABLESTATS_ID]->store(longlong(table->id), TRUE)); + + OK(field_store_string(fields[SYS_TABLESTATS_NAME], + table->name.m_name)); + OK(fields[SYS_TABLESTATS_INIT]->store(table->stat_initialized, true)); + + if (table->stat_initialized) + { + OK(fields[SYS_TABLESTATS_NROW]->store(table->stat_n_rows, true)); + + OK(fields[SYS_TABLESTATS_CLUST_SIZE]-> + store(table->stat_clustered_index_size, true)); + + OK(fields[SYS_TABLESTATS_INDEX_SIZE]-> + store(table->stat_sum_of_other_index_sizes, true)); + + OK(fields[SYS_TABLESTATS_MODIFIED]-> + store(table->stat_modified_counter, true)); + } + else + { + OK(fields[SYS_TABLESTATS_NROW]->store(0, true)); + OK(fields[SYS_TABLESTATS_CLUST_SIZE]->store(0, true)); + OK(fields[SYS_TABLESTATS_INDEX_SIZE]->store(0, true)); + OK(fields[SYS_TABLESTATS_MODIFIED]->store(0, true)); + } + + OK(fields[SYS_TABLESTATS_AUTONINC]->store(table->autoinc, true)); + + OK(fields[SYS_TABLESTATS_TABLE_REF_COUNT]-> + store(table->get_ref_count(), true)); + } + + OK(schema_table_store_record(thd, table_to_fill)); + DBUG_RETURN(0); +} + +/*******************************************************************//** +Function to go through each record in SYS_TABLES table, and fill the +information_schema.innodb_sys_tablestats table with table statistics +related information +@return 0 on success */ +static +int +i_s_sys_tables_fill_table_stats( +/*============================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + btr_pcur_t pcur; + const rec_t* rec; + mtr_t mtr; + + DBUG_ENTER("i_s_sys_tables_fill_table_stats"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + + rec = dict_startscan_system(&pcur, &mtr, dict_sys.sys_tables); + + while (rec) { + const char* err_msg; + dict_table_t* table_rec = nullptr; + + mtr.commit(); + /* Fetch the dict_table_t structure corresponding to + this SYS_TABLES record */ + err_msg = i_s_sys_tables_rec(pcur, nullptr, nullptr, + &table_rec); + + if (UNIV_LIKELY(!err_msg)) { + i_s_dict_fill_sys_tablestats(thd, table_rec, + tables->table); + } else { + ut_ad(!table_rec); + dict_sys.unlock(); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_FIND_SYSTEM_REC, "%s", + err_msg); + } + + /* Get the next record */ + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + + rec = dict_getnext_system(&pcur, &mtr); + } + + mtr.commit(); + dict_sys.unlock(); + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_sys_tablestats +@return 0 on success */ +static +int +innodb_sys_tablestats_init( +/*=======================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_tablestats_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sys_tablestats_fields_info; + schema->fill_table = i_s_sys_tables_fill_table_stats; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_tablestats = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_TABLESTATS", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB SYS_TABLESTATS", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_tablestats_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/** SYS_INDEXES **************************************************/ +/* Fields of the dynamic table INFORMATION_SCHEMA.SYS_INDEXES */ +static ST_FIELD_INFO innodb_sysindex_fields_info[]= +{ +#define SYS_INDEX_ID 0 + Column("INDEX_ID", ULonglong(), NOT_NULL), + +#define SYS_INDEX_NAME 1 + Column("NAME", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define SYS_INDEX_TABLE_ID 2 + Column("TABLE_ID", ULonglong(), NOT_NULL), + +#define SYS_INDEX_TYPE 3 + Column("TYPE", SLong(), NOT_NULL), + +#define SYS_INDEX_NUM_FIELDS 4 + Column("N_FIELDS", SLong(), NOT_NULL), + +#define SYS_INDEX_PAGE_NO 5 + Column("PAGE_NO", SLong(), NULLABLE), + +#define SYS_INDEX_SPACE 6 + Column("SPACE", SLong(), NULLABLE), + +#define SYS_INDEX_MERGE_THRESHOLD 7 + Column("MERGE_THRESHOLD", SLong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/**********************************************************************//** +Function to populate the information_schema.innodb_sys_indexes table with +collected index information +@return 0 on success */ +static +int +i_s_dict_fill_sys_indexes( +/*======================*/ + THD* thd, /*!< in: thread */ + table_id_t table_id, /*!< in: table id */ + ulint space_id, /*!< in: tablespace id */ + dict_index_t* index, /*!< in: populated dict_index_t + struct with index info */ + TABLE* table_to_fill) /*!< in/out: fill this table */ +{ + Field** fields; + + DBUG_ENTER("i_s_dict_fill_sys_indexes"); + + fields = table_to_fill->field; + + if (*index->name == *TEMP_INDEX_PREFIX_STR) { + /* Since TEMP_INDEX_PREFIX_STR is not valid UTF-8, we + need to convert it to something else. */ + *const_cast<char*>(index->name()) = '?'; + } + + OK(fields[SYS_INDEX_NAME]->store(index->name, + uint(strlen(index->name)), + system_charset_info)); + + OK(fields[SYS_INDEX_ID]->store(longlong(index->id), true)); + + OK(fields[SYS_INDEX_TABLE_ID]->store(longlong(table_id), true)); + + OK(fields[SYS_INDEX_TYPE]->store(index->type, true)); + + OK(fields[SYS_INDEX_NUM_FIELDS]->store(index->n_fields)); + + /* FIL_NULL is ULINT32_UNDEFINED */ + if (index->page == FIL_NULL) { + fields[SYS_INDEX_PAGE_NO]->set_null(); + } else { + fields[SYS_INDEX_PAGE_NO]->set_notnull(); + OK(fields[SYS_INDEX_PAGE_NO]->store(index->page, true)); + } + + if (space_id == FIL_NULL) { + fields[SYS_INDEX_SPACE]->set_null(); + } else { + fields[SYS_INDEX_SPACE]->set_notnull(); + OK(fields[SYS_INDEX_SPACE]->store(space_id, true)); + } + + OK(fields[SYS_INDEX_MERGE_THRESHOLD]->store(index->merge_threshold, + true)); + + OK(schema_table_store_record(thd, table_to_fill)); + + DBUG_RETURN(0); +} +/*******************************************************************//** +Function to go through each record in SYS_INDEXES table, and fill the +information_schema.innodb_sys_indexes table with related index information +@return 0 on success */ +static +int +i_s_sys_indexes_fill_table( +/*=======================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + btr_pcur_t pcur; + const rec_t* rec; + mem_heap_t* heap; + mtr_t mtr; + + DBUG_ENTER("i_s_sys_indexes_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + heap = mem_heap_create(1000); + dict_sys.lock(SRW_LOCK_CALL); + mtr_start(&mtr); + + /* Start scan the SYS_INDEXES table */ + rec = dict_startscan_system(&pcur, &mtr, dict_sys.sys_indexes); + + /* Process each record in the table */ + while (rec) { + const char* err_msg; + table_id_t table_id; + ulint space_id; + dict_index_t index_rec; + + /* Populate a dict_index_t structure with information from + a SYS_INDEXES row */ + err_msg = dict_process_sys_indexes_rec(heap, rec, &index_rec, + &table_id); + const byte* field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_INDEXES__SPACE, &space_id); + space_id = space_id == 4 ? mach_read_from_4(field) + : ULINT_UNDEFINED; + mtr.commit(); + dict_sys.unlock(); + + if (!err_msg) { + if (int err = i_s_dict_fill_sys_indexes( + thd, table_id, space_id, &index_rec, + tables->table)) { + mem_heap_free(heap); + DBUG_RETURN(err); + } + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_FIND_SYSTEM_REC, "%s", + err_msg); + } + + mem_heap_empty(heap); + + /* Get the next record */ + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + rec = dict_getnext_system(&pcur, &mtr); + } + + mtr.commit(); + dict_sys.unlock(); + mem_heap_free(heap); + + DBUG_RETURN(0); +} +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_sys_indexes +@return 0 on success */ +static +int +innodb_sys_indexes_init( +/*====================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_indexes_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sysindex_fields_info; + schema->fill_table = i_s_sys_indexes_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_indexes = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_INDEXES", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB SYS_INDEXES", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_indexes_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/** SYS_COLUMNS **************************************************/ +/* Fields of the dynamic table INFORMATION_SCHEMA.INNODB_SYS_COLUMNS */ +static ST_FIELD_INFO innodb_sys_columns_fields_info[]= +{ +#define SYS_COLUMN_TABLE_ID 0 + Column("TABLE_ID", ULonglong(), NOT_NULL), + +#define SYS_COLUMN_NAME 1 + Column("NAME", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define SYS_COLUMN_POSITION 2 + Column("POS", ULonglong(), NOT_NULL), + +#define SYS_COLUMN_MTYPE 3 + Column("MTYPE", SLong(), NOT_NULL), + +#define SYS_COLUMN__PRTYPE 4 + Column("PRTYPE", SLong(), NOT_NULL), + +#define SYS_COLUMN_COLUMN_LEN 5 + Column("LEN", SLong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/**********************************************************************//** +Function to populate the information_schema.innodb_sys_columns with +related column information +@return 0 on success */ +static +int +i_s_dict_fill_sys_columns( +/*======================*/ + THD* thd, /*!< in: thread */ + table_id_t table_id, /*!< in: table ID */ + const char* col_name, /*!< in: column name */ + dict_col_t* column, /*!< in: dict_col_t struct holding + more column information */ + ulint nth_v_col, /*!< in: virtual column, its + sequence number (nth virtual col) */ + TABLE* table_to_fill) /*!< in/out: fill this table */ +{ + Field** fields; + + DBUG_ENTER("i_s_dict_fill_sys_columns"); + + fields = table_to_fill->field; + + OK(fields[SYS_COLUMN_TABLE_ID]->store((longlong) table_id, TRUE)); + + OK(field_store_string(fields[SYS_COLUMN_NAME], col_name)); + + if (column->is_virtual()) { + ulint pos = dict_create_v_col_pos(nth_v_col, column->ind); + OK(fields[SYS_COLUMN_POSITION]->store(pos, true)); + } else { + OK(fields[SYS_COLUMN_POSITION]->store(column->ind, true)); + } + + OK(fields[SYS_COLUMN_MTYPE]->store(column->mtype)); + + OK(fields[SYS_COLUMN__PRTYPE]->store(column->prtype)); + + OK(fields[SYS_COLUMN_COLUMN_LEN]->store(column->len)); + + OK(schema_table_store_record(thd, table_to_fill)); + + DBUG_RETURN(0); +} +/*******************************************************************//** +Function to fill information_schema.innodb_sys_columns with information +collected by scanning SYS_COLUMNS table. +@return 0 on success */ +static +int +i_s_sys_columns_fill_table( +/*=======================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + btr_pcur_t pcur; + const rec_t* rec; + const char* col_name; + mem_heap_t* heap; + mtr_t mtr; + + DBUG_ENTER("i_s_sys_columns_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + heap = mem_heap_create(1000); + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + + rec = dict_startscan_system(&pcur, &mtr, dict_sys.sys_columns); + + while (rec) { + const char* err_msg; + dict_col_t column_rec; + table_id_t table_id; + ulint nth_v_col; + + /* populate a dict_col_t structure with information from + a SYS_COLUMNS row */ + err_msg = dict_process_sys_columns_rec(heap, rec, &column_rec, + &table_id, &col_name, + &nth_v_col); + + mtr.commit(); + dict_sys.unlock(); + + if (!err_msg) { + i_s_dict_fill_sys_columns(thd, table_id, col_name, + &column_rec, nth_v_col, + tables->table); + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_FIND_SYSTEM_REC, "%s", + err_msg); + } + + mem_heap_empty(heap); + + /* Get the next record */ + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + rec = dict_getnext_system(&pcur, &mtr); + } + + mtr.commit(); + dict_sys.unlock(); + mem_heap_free(heap); + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_sys_columns +@return 0 on success */ +static +int +innodb_sys_columns_init( +/*====================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_columns_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sys_columns_fields_info; + schema->fill_table = i_s_sys_columns_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_columns = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_COLUMNS", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB SYS_COLUMNS", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_columns_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/** SYS_VIRTUAL **************************************************/ +/** Fields of the dynamic table INFORMATION_SCHEMA.INNODB_SYS_VIRTUAL */ +static ST_FIELD_INFO innodb_sys_virtual_fields_info[]= +{ +#define SYS_VIRTUAL_TABLE_ID 0 + Column("TABLE_ID", ULonglong(), NOT_NULL), + +#define SYS_VIRTUAL_POS 1 + Column("POS", ULong(), NOT_NULL), + +#define SYS_VIRTUAL_BASE_POS 2 + Column("BASE_POS", ULong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/** Function to populate the information_schema.innodb_sys_virtual with +related information +param[in] thd thread +param[in] table_id table ID +param[in] pos virtual column position +param[in] base_pos base column position +param[in,out] table_to_fill fill this table +@return 0 on success */ +static +int +i_s_dict_fill_sys_virtual( + THD* thd, + table_id_t table_id, + ulint pos, + ulint base_pos, + TABLE* table_to_fill) +{ + Field** fields; + + DBUG_ENTER("i_s_dict_fill_sys_virtual"); + + fields = table_to_fill->field; + + OK(fields[SYS_VIRTUAL_TABLE_ID]->store(table_id, true)); + + OK(fields[SYS_VIRTUAL_POS]->store(pos, true)); + + OK(fields[SYS_VIRTUAL_BASE_POS]->store(base_pos, true)); + + OK(schema_table_store_record(thd, table_to_fill)); + + DBUG_RETURN(0); +} + +/** Function to fill information_schema.innodb_sys_virtual with information +collected by scanning SYS_VIRTUAL table. +param[in] thd thread +param[in,out] tables tables to fill +param[in] item condition (not used) +@return 0 on success */ +static +int +i_s_sys_virtual_fill_table( + THD* thd, + TABLE_LIST* tables, + Item* ) +{ + btr_pcur_t pcur; + const rec_t* rec; + ulint pos; + ulint base_pos; + mtr_t mtr; + + DBUG_ENTER("i_s_sys_virtual_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL) || !dict_sys.sys_virtual) { + DBUG_RETURN(0); + } + + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + + rec = dict_startscan_system(&pcur, &mtr, dict_sys.sys_virtual); + + while (rec) { + const char* err_msg; + table_id_t table_id; + + /* populate a dict_col_t structure with information from + a SYS_VIRTUAL row */ + err_msg = dict_process_sys_virtual_rec(rec, + &table_id, &pos, + &base_pos); + + mtr.commit(); + dict_sys.unlock(); + + if (!err_msg) { + i_s_dict_fill_sys_virtual(thd, table_id, pos, base_pos, + tables->table); + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_FIND_SYSTEM_REC, "%s", + err_msg); + } + + /* Get the next record */ + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + rec = dict_getnext_system(&pcur, &mtr); + } + + mtr.commit(); + dict_sys.unlock(); + + DBUG_RETURN(0); +} + +/** Bind the dynamic table INFORMATION_SCHEMA.innodb_sys_virtual +param[in,out] p table schema object +@return 0 on success */ +static +int +innodb_sys_virtual_init( + void* p) +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_virtual_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sys_virtual_fields_info; + schema->fill_table = i_s_sys_virtual_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_virtual = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_VIRTUAL", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB SYS_VIRTUAL", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_virtual_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + + +namespace Show { +/** SYS_FIELDS ***************************************************/ +/* Fields of the dynamic table INFORMATION_SCHEMA.INNODB_SYS_FIELDS */ +static ST_FIELD_INFO innodb_sys_fields_fields_info[]= +{ +#define SYS_FIELD_INDEX_ID 0 + Column("INDEX_ID", ULonglong(), NOT_NULL), + +#define SYS_FIELD_NAME 1 + Column("NAME", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define SYS_FIELD_POS 2 + Column("POS", ULong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/**********************************************************************//** +Function to fill information_schema.innodb_sys_fields with information +collected by scanning SYS_FIELDS table. +@return 0 on success */ +static +int +i_s_dict_fill_sys_fields( +/*=====================*/ + THD* thd, /*!< in: thread */ + index_id_t index_id, /*!< in: index id for the field */ + dict_field_t* field, /*!< in: table */ + ulint pos, /*!< in: Field position */ + TABLE* table_to_fill) /*!< in/out: fill this table */ +{ + Field** fields; + + DBUG_ENTER("i_s_dict_fill_sys_fields"); + + fields = table_to_fill->field; + + OK(fields[SYS_FIELD_INDEX_ID]->store(index_id, true)); + + OK(field_store_string(fields[SYS_FIELD_NAME], field->name)); + + OK(fields[SYS_FIELD_POS]->store(pos, true)); + + OK(schema_table_store_record(thd, table_to_fill)); + + DBUG_RETURN(0); +} +/*******************************************************************//** +Function to go through each record in SYS_FIELDS table, and fill the +information_schema.innodb_sys_fields table with related index field +information +@return 0 on success */ +static +int +i_s_sys_fields_fill_table( +/*======================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + btr_pcur_t pcur; + const rec_t* rec; + mem_heap_t* heap; + index_id_t last_id; + mtr_t mtr; + + DBUG_ENTER("i_s_sys_fields_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + heap = mem_heap_create(1000); + mtr.start(); + + /* will save last index id so that we know whether we move to + the next index. This is used to calculate prefix length */ + last_id = 0; + + dict_sys.lock(SRW_LOCK_CALL); + rec = dict_startscan_system(&pcur, &mtr, dict_sys.sys_fields); + + while (rec) { + ulint pos; + const char* err_msg; + index_id_t index_id; + dict_field_t field_rec; + + /* Populate a dict_field_t structure with information from + a SYS_FIELDS row */ + err_msg = dict_process_sys_fields_rec(heap, rec, &field_rec, + &pos, &index_id, last_id); + + mtr.commit(); + dict_sys.unlock(); + + if (!err_msg) { + i_s_dict_fill_sys_fields(thd, index_id, &field_rec, + pos, tables->table); + last_id = index_id; + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_FIND_SYSTEM_REC, "%s", + err_msg); + } + + mem_heap_empty(heap); + + /* Get the next record */ + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + rec = dict_getnext_system(&pcur, &mtr); + } + + mtr.commit(); + dict_sys.unlock(); + mem_heap_free(heap); + + DBUG_RETURN(0); +} +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_sys_fields +@return 0 on success */ +static +int +innodb_sys_fields_init( +/*===================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_field_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sys_fields_fields_info; + schema->fill_table = i_s_sys_fields_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_fields = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_FIELDS", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB SYS_FIELDS", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_fields_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/** SYS_FOREIGN ********************************************/ +/* Fields of the dynamic table INFORMATION_SCHEMA.INNODB_SYS_FOREIGN */ +static ST_FIELD_INFO innodb_sys_foreign_fields_info[]= +{ +#define SYS_FOREIGN_ID 0 + Column("ID", Varchar(NAME_LEN + 1), NOT_NULL), + +#define SYS_FOREIGN_FOR_NAME 1 + Column("FOR_NAME", Varchar(NAME_LEN + 1), NOT_NULL), + +#define SYS_FOREIGN_REF_NAME 2 + Column("REF_NAME", Varchar(NAME_LEN + 1), NOT_NULL), + +#define SYS_FOREIGN_NUM_COL 3 + Column("N_COLS", ULong(), NOT_NULL), + +#define SYS_FOREIGN_TYPE 4 + Column("TYPE", ULong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/**********************************************************************//** +Function to fill information_schema.innodb_sys_foreign with information +collected by scanning SYS_FOREIGN table. +@return 0 on success */ +static +int +i_s_dict_fill_sys_foreign( +/*======================*/ + THD* thd, /*!< in: thread */ + dict_foreign_t* foreign, /*!< in: table */ + TABLE* table_to_fill) /*!< in/out: fill this table */ +{ + Field** fields; + + DBUG_ENTER("i_s_dict_fill_sys_foreign"); + + fields = table_to_fill->field; + + OK(field_store_string(fields[SYS_FOREIGN_ID], foreign->id)); + + OK(field_store_string(fields[SYS_FOREIGN_FOR_NAME], + foreign->foreign_table_name)); + + OK(field_store_string(fields[SYS_FOREIGN_REF_NAME], + foreign->referenced_table_name)); + + OK(fields[SYS_FOREIGN_NUM_COL]->store(foreign->n_fields)); + + OK(fields[SYS_FOREIGN_TYPE]->store(foreign->type)); + + OK(schema_table_store_record(thd, table_to_fill)); + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Function to populate INFORMATION_SCHEMA.innodb_sys_foreign table. Loop +through each record in SYS_FOREIGN, and extract the foreign key +information. +@return 0 on success */ +static +int +i_s_sys_foreign_fill_table( +/*=======================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + btr_pcur_t pcur; + const rec_t* rec; + mem_heap_t* heap; + mtr_t mtr; + + DBUG_ENTER("i_s_sys_foreign_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL) || !dict_sys.sys_foreign) { + DBUG_RETURN(0); + } + + heap = mem_heap_create(1000); + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + + rec = dict_startscan_system(&pcur, &mtr, dict_sys.sys_foreign); + + while (rec) { + const char* err_msg; + dict_foreign_t foreign_rec; + + /* Populate a dict_foreign_t structure with information from + a SYS_FOREIGN row */ + err_msg = dict_process_sys_foreign_rec(heap, rec, &foreign_rec); + + mtr.commit(); + dict_sys.unlock(); + + if (!err_msg) { + i_s_dict_fill_sys_foreign(thd, &foreign_rec, + tables->table); + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_FIND_SYSTEM_REC, "%s", + err_msg); + } + + mem_heap_empty(heap); + + /* Get the next record */ + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + rec = dict_getnext_system(&pcur, &mtr); + } + + mtr.commit(); + dict_sys.unlock(); + mem_heap_free(heap); + + DBUG_RETURN(0); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_sys_foreign +@return 0 on success */ +static +int +innodb_sys_foreign_init( +/*====================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_foreign_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sys_foreign_fields_info; + schema->fill_table = i_s_sys_foreign_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_foreign = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_FOREIGN", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB SYS_FOREIGN", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_foreign_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/** SYS_FOREIGN_COLS ********************************************/ +/* Fields of the dynamic table INFORMATION_SCHEMA.INNODB_SYS_FOREIGN_COLS */ +static ST_FIELD_INFO innodb_sys_foreign_cols_fields_info[]= +{ +#define SYS_FOREIGN_COL_ID 0 + Column("ID", Varchar(NAME_LEN + 1), NOT_NULL), + +#define SYS_FOREIGN_COL_FOR_NAME 1 + Column("FOR_COL_NAME", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define SYS_FOREIGN_COL_REF_NAME 2 + Column("REF_COL_NAME", Varchar(NAME_CHAR_LEN), NOT_NULL), + +#define SYS_FOREIGN_COL_POS 3 + Column("POS", ULong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/**********************************************************************//** +Function to fill information_schema.innodb_sys_foreign_cols with information +collected by scanning SYS_FOREIGN_COLS table. +@return 0 on success */ +static +int +i_s_dict_fill_sys_foreign_cols( +/*==========================*/ + THD* thd, /*!< in: thread */ + const char* name, /*!< in: foreign key constraint name */ + const char* for_col_name, /*!< in: referencing column name*/ + const char* ref_col_name, /*!< in: referenced column + name */ + ulint pos, /*!< in: column position */ + TABLE* table_to_fill) /*!< in/out: fill this table */ +{ + Field** fields; + + DBUG_ENTER("i_s_dict_fill_sys_foreign_cols"); + + fields = table_to_fill->field; + + OK(field_store_string(fields[SYS_FOREIGN_COL_ID], name)); + + OK(field_store_string(fields[SYS_FOREIGN_COL_FOR_NAME], for_col_name)); + + OK(field_store_string(fields[SYS_FOREIGN_COL_REF_NAME], ref_col_name)); + + OK(fields[SYS_FOREIGN_COL_POS]->store(pos, true)); + + OK(schema_table_store_record(thd, table_to_fill)); + + DBUG_RETURN(0); +} +/*******************************************************************//** +Function to populate INFORMATION_SCHEMA.innodb_sys_foreign_cols table. Loop +through each record in SYS_FOREIGN_COLS, and extract the foreign key column +information and fill the INFORMATION_SCHEMA.innodb_sys_foreign_cols table. +@return 0 on success */ +static +int +i_s_sys_foreign_cols_fill_table( +/*============================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + btr_pcur_t pcur; + const rec_t* rec; + mem_heap_t* heap; + mtr_t mtr; + + DBUG_ENTER("i_s_sys_foreign_cols_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL) + || !dict_sys.sys_foreign_cols) { + DBUG_RETURN(0); + } + + heap = mem_heap_create(1000); + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + + rec = dict_startscan_system(&pcur, &mtr, dict_sys.sys_foreign_cols); + + while (rec) { + const char* err_msg; + const char* name; + const char* for_col_name; + const char* ref_col_name; + ulint pos; + + /* Extract necessary information from a SYS_FOREIGN_COLS row */ + err_msg = dict_process_sys_foreign_col_rec( + heap, rec, &name, &for_col_name, &ref_col_name, &pos); + + mtr.commit(); + dict_sys.unlock(); + + if (!err_msg) { + i_s_dict_fill_sys_foreign_cols( + thd, name, for_col_name, ref_col_name, pos, + tables->table); + } else { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_FIND_SYSTEM_REC, "%s", + err_msg); + } + + mem_heap_empty(heap); + + /* Get the next record */ + mtr.start(); + dict_sys.lock(SRW_LOCK_CALL); + rec = dict_getnext_system(&pcur, &mtr); + } + + mtr.commit(); + dict_sys.unlock(); + mem_heap_free(heap); + + DBUG_RETURN(0); +} +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.innodb_sys_foreign_cols +@return 0 on success */ +static +int +innodb_sys_foreign_cols_init( +/*========================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_foreign_cols_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sys_foreign_cols_fields_info; + schema->fill_table = i_s_sys_foreign_cols_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_foreign_cols = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_FOREIGN_COLS", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB SYS_FOREIGN_COLS", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_foreign_cols_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/** SYS_TABLESPACES ********************************************/ +/* Fields of the dynamic table INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES */ +static ST_FIELD_INFO innodb_sys_tablespaces_fields_info[]= +{ +#define SYS_TABLESPACES_SPACE 0 + Column("SPACE", ULong(), NOT_NULL), + +#define SYS_TABLESPACES_NAME 1 + Column("NAME", Varchar(MAX_FULL_NAME_LEN + 1), NOT_NULL), + +#define SYS_TABLESPACES_FLAGS 2 + Column("FLAG", ULong(), NOT_NULL), + +#define SYS_TABLESPACES_ROW_FORMAT 3 + Column("ROW_FORMAT", Varchar(22), NULLABLE), + +#define SYS_TABLESPACES_PAGE_SIZE 4 + Column("PAGE_SIZE", ULong(), NOT_NULL), + +#define SYS_TABLESPACES_FILENAME 5 + Column("FILENAME", Varchar(FN_REFLEN), NOT_NULL), + +#define SYS_TABLESPACES_FS_BLOCK_SIZE 6 + Column("FS_BLOCK_SIZE", ULong(),NOT_NULL), + +#define SYS_TABLESPACES_FILE_SIZE 7 + Column("FILE_SIZE", ULonglong(), NOT_NULL), + +#define SYS_TABLESPACES_ALLOC_SIZE 8 + Column("ALLOCATED_SIZE", ULonglong(), NOT_NULL), + + CEnd() +}; +} // namespace Show + +extern size_t os_file_get_fs_block_size(const char *path); + +/** Produce one row of INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES. +@param thd connection +@param s tablespace +@param t output table +@return 0 on success */ +static int i_s_sys_tablespaces_fill(THD *thd, const fil_space_t &s, TABLE *t) +{ + DBUG_ENTER("i_s_sys_tablespaces_fill"); + const char *row_format; + + if (s.full_crc32() || is_system_tablespace(s.id)) + row_format= nullptr; + else if (FSP_FLAGS_GET_ZIP_SSIZE(s.flags)) + row_format= "Compressed"; + else if (FSP_FLAGS_HAS_ATOMIC_BLOBS(s.flags)) + row_format= "Dynamic"; + else + row_format= "Compact or Redundant"; + + Field **fields= t->field; + + OK(fields[SYS_TABLESPACES_SPACE]->store(s.id, true)); + { + Field *f= fields[SYS_TABLESPACES_NAME]; + const auto name= s.name(); + if (name.data()) + { + OK(f->store(name.data(), name.size(), system_charset_info)); + f->set_notnull(); + } + else if (srv_is_undo_tablespace(s.id)) + { + char name[15]; + snprintf(name, sizeof name, "innodb_undo%03u", + (s.id - srv_undo_space_id_start + 1)); + OK(f->store(name, strlen(name), system_charset_info)); + } else f->set_notnull(); + } + + fields[SYS_TABLESPACES_NAME]->set_null(); + OK(fields[SYS_TABLESPACES_FLAGS]->store(s.flags, true)); + OK(field_store_string(fields[SYS_TABLESPACES_ROW_FORMAT], row_format)); + const char *filepath= s.chain.start->name; + OK(field_store_string(fields[SYS_TABLESPACES_FILENAME], filepath)); + + OK(fields[SYS_TABLESPACES_PAGE_SIZE]->store(s.physical_size(), true)); + size_t fs_block_size; + os_file_size_t file= os_file_get_size(filepath); + if (file.m_total_size == os_offset_t(~0)) + { + file.m_total_size= 0; + file.m_alloc_size= 0; + fs_block_size= 0; + } + else + fs_block_size= os_file_get_fs_block_size(filepath); + + OK(fields[SYS_TABLESPACES_FS_BLOCK_SIZE]->store(fs_block_size, true)); + OK(fields[SYS_TABLESPACES_FILE_SIZE]->store(file.m_total_size, true)); + OK(fields[SYS_TABLESPACES_ALLOC_SIZE]->store(file.m_alloc_size, true)); + + OK(schema_table_store_record(thd, t)); + + DBUG_RETURN(0); +} + +/** Populate INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES. +@param thd connection +@param tables table to fill +@return 0 on success */ +static int i_s_sys_tablespaces_fill_table(THD *thd, TABLE_LIST *tables, Item*) +{ + DBUG_ENTER("i_s_sys_tablespaces_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + if (check_global_access(thd, PROCESS_ACL)) + DBUG_RETURN(0); + + int err= 0; + + mysql_mutex_lock(&fil_system.mutex); + fil_system.freeze_space_list++; + + for (fil_space_t &space : fil_system.space_list) + { + if (space.purpose == FIL_TYPE_TABLESPACE && !space.is_stopping() && + space.chain.start) + { + space.reacquire(); + mysql_mutex_unlock(&fil_system.mutex); + space.s_lock(); + err= i_s_sys_tablespaces_fill(thd, space, tables->table); + space.s_unlock(); + mysql_mutex_lock(&fil_system.mutex); + space.release(); + if (err) + break; + } + } + + fil_system.freeze_space_list--; + mysql_mutex_unlock(&fil_system.mutex); + if (err == DB_SUCCESS) + err= i_s_sys_tablespaces_fill(thd, *fil_system.temp_space, tables->table); + DBUG_RETURN(err); +} + +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES +@return 0 on success */ +static +int +innodb_sys_tablespaces_init( +/*========================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_sys_tablespaces_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_sys_tablespaces_fields_info; + schema->fill_table = i_s_sys_tablespaces_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_sys_tablespaces = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_SYS_TABLESPACES", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + plugin_author, + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB tablespaces", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_GPL, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_sys_tablespaces_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; + +namespace Show { +/** TABLESPACES_ENCRYPTION ********************************************/ +/* Fields of the table INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION */ +static ST_FIELD_INFO innodb_tablespaces_encryption_fields_info[]= +{ +#define TABLESPACES_ENCRYPTION_SPACE 0 + Column("SPACE", ULong(), NOT_NULL), + +#define TABLESPACES_ENCRYPTION_NAME 1 + Column("NAME", Varchar(MAX_FULL_NAME_LEN + 1), NULLABLE), + +#define TABLESPACES_ENCRYPTION_ENCRYPTION_SCHEME 2 + Column("ENCRYPTION_SCHEME", ULong(), NOT_NULL), + +#define TABLESPACES_ENCRYPTION_KEYSERVER_REQUESTS 3 + Column("KEYSERVER_REQUESTS", ULong(), NOT_NULL), + +#define TABLESPACES_ENCRYPTION_MIN_KEY_VERSION 4 + Column("MIN_KEY_VERSION", ULong(), NOT_NULL), + +#define TABLESPACES_ENCRYPTION_CURRENT_KEY_VERSION 5 + Column("CURRENT_KEY_VERSION", ULong(), NOT_NULL), + +#define TABLESPACES_ENCRYPTION_KEY_ROTATION_PAGE_NUMBER 6 + Column("KEY_ROTATION_PAGE_NUMBER", ULonglong(), NULLABLE), + +#define TABLESPACES_ENCRYPTION_KEY_ROTATION_MAX_PAGE_NUMBER 7 + Column("KEY_ROTATION_MAX_PAGE_NUMBER", ULonglong(), NULLABLE), + +#define TABLESPACES_ENCRYPTION_CURRENT_KEY_ID 8 + Column("CURRENT_KEY_ID", ULong(), NOT_NULL), + +#define TABLESPACES_ENCRYPTION_ROTATING_OR_FLUSHING 9 + Column("ROTATING_OR_FLUSHING", SLong(1), NOT_NULL), + + CEnd() +}; +} // namespace Show + +/**********************************************************************//** +Function to fill INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION. +@param[in] thd thread handle +@param[in] space Tablespace +@param[in] table_to_fill I_S table to fill +@return 0 on success */ +static +int +i_s_dict_fill_tablespaces_encryption( + THD* thd, + fil_space_t* space, + TABLE* table_to_fill) +{ + Field** fields; + struct fil_space_crypt_status_t status; + DBUG_ENTER("i_s_dict_fill_tablespaces_encryption"); + + fields = table_to_fill->field; + + fil_space_crypt_get_status(space, &status); + + /* If tablespace id does not match, we did not find + encryption information for this tablespace. */ + if (!space->crypt_data || space->id != status.space) { + goto skip; + } + + OK(fields[TABLESPACES_ENCRYPTION_SPACE]->store(space->id, true)); + + { + const auto name = space->name(); + if (name.data()) { + OK(fields[TABLESPACES_ENCRYPTION_NAME]->store( + name.data(), name.size(), + system_charset_info)); + fields[TABLESPACES_ENCRYPTION_NAME]->set_notnull(); + } else if (srv_is_undo_tablespace(space->id)) { + char undo_name[sizeof "innodb_undo000"]; + snprintf(undo_name, sizeof undo_name, + "innodb_undo%03" PRIu32, space->id); + OK(fields[TABLESPACES_ENCRYPTION_NAME]->store( + undo_name, strlen(undo_name), + system_charset_info)); + fields[TABLESPACES_ENCRYPTION_NAME]->set_notnull(); + } else { + fields[TABLESPACES_ENCRYPTION_NAME]->set_null(); + } + } + + OK(fields[TABLESPACES_ENCRYPTION_ENCRYPTION_SCHEME]->store( + status.scheme, true)); + OK(fields[TABLESPACES_ENCRYPTION_KEYSERVER_REQUESTS]->store( + status.keyserver_requests, true)); + OK(fields[TABLESPACES_ENCRYPTION_MIN_KEY_VERSION]->store( + status.min_key_version, true)); + OK(fields[TABLESPACES_ENCRYPTION_CURRENT_KEY_VERSION]->store( + status.current_key_version, true)); + OK(fields[TABLESPACES_ENCRYPTION_CURRENT_KEY_ID]->store( + status.key_id, true)); + OK(fields[TABLESPACES_ENCRYPTION_ROTATING_OR_FLUSHING]->store( + status.rotating || status.flushing, true)); + + if (status.rotating) { + fields[TABLESPACES_ENCRYPTION_KEY_ROTATION_PAGE_NUMBER]->set_notnull(); + OK(fields[TABLESPACES_ENCRYPTION_KEY_ROTATION_PAGE_NUMBER]->store( + status.rotate_next_page_number, true)); + fields[TABLESPACES_ENCRYPTION_KEY_ROTATION_MAX_PAGE_NUMBER]->set_notnull(); + OK(fields[TABLESPACES_ENCRYPTION_KEY_ROTATION_MAX_PAGE_NUMBER]->store( + status.rotate_max_page_number, true)); + } else { + fields[TABLESPACES_ENCRYPTION_KEY_ROTATION_PAGE_NUMBER] + ->set_null(); + fields[TABLESPACES_ENCRYPTION_KEY_ROTATION_MAX_PAGE_NUMBER] + ->set_null(); + } + + OK(schema_table_store_record(thd, table_to_fill)); + +skip: + DBUG_RETURN(0); +} +/*******************************************************************//** +Function to populate INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION table. +Loop through each record in TABLESPACES_ENCRYPTION, and extract the column +information and fill the INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION table. +@return 0 on success */ +static +int +i_s_tablespaces_encryption_fill_table( +/*===========================*/ + THD* thd, /*!< in: thread */ + TABLE_LIST* tables, /*!< in/out: tables to fill */ + Item* ) /*!< in: condition (not used) */ +{ + DBUG_ENTER("i_s_tablespaces_encryption_fill_table"); + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str); + + /* deny access to user without PROCESS_ACL privilege */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + int err = 0; + mysql_mutex_lock(&fil_system.mutex); + fil_system.freeze_space_list++; + + for (fil_space_t& space : fil_system.space_list) { + if (space.purpose == FIL_TYPE_TABLESPACE + && !space.is_stopping()) { + space.reacquire(); + mysql_mutex_unlock(&fil_system.mutex); + space.s_lock(); + err = i_s_dict_fill_tablespaces_encryption( + thd, &space, tables->table); + space.s_unlock(); + mysql_mutex_lock(&fil_system.mutex); + space.release(); + if (err) { + break; + } + } + } + + fil_system.freeze_space_list--; + mysql_mutex_unlock(&fil_system.mutex); + DBUG_RETURN(err); +} +/*******************************************************************//** +Bind the dynamic table INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION +@return 0 on success */ +static +int +innodb_tablespaces_encryption_init( +/*========================*/ + void* p) /*!< in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_tablespaces_encryption_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = Show::innodb_tablespaces_encryption_fields_info; + schema->fill_table = i_s_tablespaces_encryption_fill_table; + + DBUG_RETURN(0); +} + +struct st_maria_plugin i_s_innodb_tablespaces_encryption = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + MYSQL_INFORMATION_SCHEMA_PLUGIN, + + /* pointer to type-specific plugin descriptor */ + /* void* */ + &i_s_info, + + /* plugin name */ + /* const char* */ + "INNODB_TABLESPACES_ENCRYPTION", + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + "Google Inc", + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + "InnoDB TABLESPACES_ENCRYPTION", + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + PLUGIN_LICENSE_BSD, + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + innodb_tablespaces_encryption_init, + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + i_s_common_deinit, + + i_s_version, nullptr, nullptr, PACKAGE_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +}; diff --git a/storage/innobase/handler/i_s.h b/storage/innobase/handler/i_s.h new file mode 100644 index 00000000..c8190a41 --- /dev/null +++ b/storage/innobase/handler/i_s.h @@ -0,0 +1,91 @@ +/***************************************************************************** + +Copyright (c) 2007, 2015, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2014, 2021, MariaDB Corporation. + +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 Street, Fifth Floor, Boston, MA 02110-1335 USA + +*****************************************************************************/ + +/**************************************************//** +@file handler/i_s.h +InnoDB INFORMATION SCHEMA tables interface to MySQL. + +Created July 18, 2007 Vasil Dimov +Modified Dec 29, 2014 Jan Lindström +*******************************************************/ + +#ifndef i_s_h +#define i_s_h +#include "dict0types.h" + +const char plugin_author[] = "Oracle Corporation"; +const char maria_plugin_author[] = "MariaDB Corporation"; + +extern struct st_maria_plugin i_s_innodb_trx; +extern struct st_maria_plugin i_s_innodb_locks; +extern struct st_maria_plugin i_s_innodb_lock_waits; +extern struct st_maria_plugin i_s_innodb_cmp; +extern struct st_maria_plugin i_s_innodb_cmp_reset; +extern struct st_maria_plugin i_s_innodb_cmp_per_index; +extern struct st_maria_plugin i_s_innodb_cmp_per_index_reset; +extern struct st_maria_plugin i_s_innodb_cmpmem; +extern struct st_maria_plugin i_s_innodb_cmpmem_reset; +extern struct st_maria_plugin i_s_innodb_metrics; +extern struct st_maria_plugin i_s_innodb_ft_default_stopword; +extern struct st_maria_plugin i_s_innodb_ft_deleted; +extern struct st_maria_plugin i_s_innodb_ft_being_deleted; +extern struct st_maria_plugin i_s_innodb_ft_index_cache; +extern struct st_maria_plugin i_s_innodb_ft_index_table; +extern struct st_maria_plugin i_s_innodb_ft_config; +extern struct st_maria_plugin i_s_innodb_buffer_page; +extern struct st_maria_plugin i_s_innodb_buffer_page_lru; +extern struct st_maria_plugin i_s_innodb_buffer_stats; +extern struct st_maria_plugin i_s_innodb_sys_tables; +extern struct st_maria_plugin i_s_innodb_sys_tablestats; +extern struct st_maria_plugin i_s_innodb_sys_indexes; +extern struct st_maria_plugin i_s_innodb_sys_columns; +extern struct st_maria_plugin i_s_innodb_sys_fields; +extern struct st_maria_plugin i_s_innodb_sys_foreign; +extern struct st_maria_plugin i_s_innodb_sys_foreign_cols; +extern struct st_maria_plugin i_s_innodb_sys_tablespaces; +extern struct st_maria_plugin i_s_innodb_sys_virtual; +extern struct st_maria_plugin i_s_innodb_tablespaces_encryption; + +/** The latest successfully looked up innodb_fts_aux_table */ +extern table_id_t innodb_ft_aux_table_id; + +/** maximum number of buffer page info we would cache. */ +#define MAX_BUF_INFO_CACHED 10000 + +#define OK(expr) \ + if ((expr) != 0) { \ + DBUG_RETURN(1); \ + } + +#define BREAK_IF(expr) if ((expr)) break + +#define RETURN_IF_INNODB_NOT_STARTED(plugin_name) \ +do { \ + if (!srv_was_started) { \ + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, \ + ER_CANT_FIND_SYSTEM_REC, \ + "InnoDB: SELECTing from " \ + "INFORMATION_SCHEMA.%s but " \ + "the InnoDB storage engine " \ + "is not installed", plugin_name); \ + DBUG_RETURN(0); \ + } \ +} while (0) + +#endif /* i_s_h */ |