summaryrefslogtreecommitdiffstats
path: root/storage/innobase/handler
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/handler')
-rw-r--r--storage/innobase/handler/ha_innodb.cc21217
-rw-r--r--storage/innobase/handler/ha_innodb.h937
-rw-r--r--storage/innobase/handler/handler0alter.cc11843
-rw-r--r--storage/innobase/handler/i_s.cc6506
-rw-r--r--storage/innobase/handler/i_s.h91
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 */