Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: 52c63a6cbe
Fetching contributors…

Cannot retrieve contributors at this time

12328 lines (10203 sloc) 365.277 kB
/*****************************************************************************
Copyright (c) 2000, 2012, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2008, 2009 Google Inc.
Copyright (c) 2009, Percona Inc.
Copyright (c) 2012, Twitter, Inc.
Portions of this file contain modifications contributed and copyrighted by
Google, Inc. Those modifications are gratefully acknowledged and are described
briefly in the InnoDB documentation. The contributions by Google are
incorporated with their permission, and subject to the conditions contained in
the file COPYING.Google.
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, Suite 500, Boston, MA 02110-1335 USA
*****************************************************************************/
/* TODO list for the InnoDB handler in 5.0:
- fix savepoint functions to use savepoint storage area
- Find out what kind of problems the OS X case-insensitivity causes to
table and database names; should we 'normalize' the names like we do
in Windows?
*/
#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation // gcc: Class implementation
#endif
#include <sql_table.h> // explain_filename, nz2, EXPLAIN_PARTITIONS_AS_COMMENT,
// EXPLAIN_FILENAME_MAX_EXTRA_LENGTH
#include <sql_acl.h> // PROCESS_ACL
#include <m_ctype.h>
#include <debug_sync.h> // DEBUG_SYNC
#include <mysys_err.h>
#include <mysql/plugin.h>
#include <mysql/innodb_priv.h>
#include <mysql/psi/psi.h>
#include <my_sys.h>
/** @file ha_innodb.cc */
/* Include necessary InnoDB headers */
extern "C" {
#include "univ.i"
#include "buf0lru.h"
#include "btr0sea.h"
#include "os0file.h"
#include "os0thread.h"
#include "srv0start.h"
#include "srv0srv.h"
#include "trx0roll.h"
#include "trx0trx.h"
#include "trx0sys.h"
#include "mtr0mtr.h"
#include "row0ins.h"
#include "row0mysql.h"
#include "row0sel.h"
#include "row0upd.h"
#include "log0log.h"
#include "lock0lock.h"
#include "dict0crea.h"
#include "btr0cur.h"
#include "btr0btr.h"
#include "fsp0fsp.h"
#include "sync0sync.h"
#include "fil0fil.h"
#include "trx0xa.h"
#include "row0merge.h"
#include "dict0boot.h"
#include "ha_prototypes.h"
#include "ut0mem.h"
#include "ibuf0ibuf.h"
#include "buf0rea.h"
}
#include "ha_innodb.h"
#include "i_s.h"
# ifndef MYSQL_PLUGIN_IMPORT
# define MYSQL_PLUGIN_IMPORT /* nothing */
# endif /* MYSQL_PLUGIN_IMPORT */
/** to protect innobase_open_files */
static mysql_mutex_t innobase_share_mutex;
/** to force correct commit order in binlog */
static mysql_mutex_t prepare_commit_mutex;
static ulong commit_threads = 0;
static mysql_mutex_t commit_threads_m;
static mysql_cond_t commit_cond;
static mysql_mutex_t commit_cond_m;
static bool innodb_inited = 0;
#define INSIDE_HA_INNOBASE_CC
/* In the Windows plugin, the return value of current_thd is
undefined. Map it to NULL. */
#define EQ_CURRENT_THD(thd) ((thd) == current_thd)
static 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 long innobase_mirrored_log_groups, innobase_log_files_in_group,
innobase_log_buffer_size,
innobase_additional_mem_pool_size, innobase_file_io_threads,
innobase_force_recovery, innobase_open_files,
innobase_autoinc_lock_mode;
static ulong innobase_commit_concurrency = 0;
static ulong innobase_read_io_threads;
static ulong innobase_write_io_threads;
static long innobase_buffer_pool_instances = 1;
static long long innobase_buffer_pool_size, innobase_log_file_size;
/** Percentage of the buffer pool to reserve for 'old' blocks.
Connected to buf_LRU_old_ratio. */
static uint innobase_old_blocks_pct;
/* The default values for the following char* start-up parameters
are determined in innobase_init below: */
static char* innobase_data_home_dir = NULL;
static char* innobase_data_file_path = NULL;
static char* innobase_log_group_home_dir = NULL;
static char* innobase_file_format_name = NULL;
static char* innobase_change_buffering = NULL;
/* The highest file format being used in the database. The value can be
set by user, however, it will be adjusted to the newer file format if
a table of such format is created/opened. */
static char* innobase_file_format_max = NULL;
static char* innobase_file_flush_method = NULL;
/* Below we have boolean-valued start-up parameters, and their default
values */
static ulong innobase_fast_shutdown = 1;
static my_bool innobase_file_format_check = TRUE;
#ifdef UNIV_LOG_ARCHIVE
static my_bool innobase_log_archive = FALSE;
static char* innobase_log_arch_dir = NULL;
#endif /* UNIV_LOG_ARCHIVE */
static my_bool innobase_use_doublewrite = TRUE;
static my_bool innobase_use_checksums = TRUE;
static my_bool innobase_locks_unsafe_for_binlog = FALSE;
static my_bool innobase_rollback_on_timeout = FALSE;
static my_bool innobase_create_status_file = FALSE;
static my_bool innobase_stats_on_metadata = TRUE;
static my_bool innobase_large_prefix = FALSE;
static char* internal_innobase_data_file_path = NULL;
static char* innodb_version_str = (char*) INNODB_VERSION_STR;
/** 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
};
/** List of values for system variable "innodb_index_page_split_mode". */
static const char* innodb_index_page_split_mode_names[] = {
"symmetric",
"upper",
"lower",
NULL
};
static TYPELIB innodb_index_page_split_mode_typelib = {
array_elements(innodb_index_page_split_mode_names) - 1,
"innodb_index_page_split_mode_typelib",
innodb_index_page_split_mode_names,
NULL
};
/* The following counter is used to convey information to InnoDB
about server activity: in selects it is not sensible to call
srv_active_wake_master_thread after each fetch or search, we only do
it every INNOBASE_WAKE_INTERVAL'th step. */
#define INNOBASE_WAKE_INTERVAL 32
static ulong innobase_active_counter = 0;
static hash_table_t* innobase_open_tables;
/** Allowed values of innodb_change_buffering */
static const char* innobase_change_buffering_values[IBUF_USE_COUNT] = {
"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 */
};
#ifdef HAVE_PSI_INTERFACE
/* Keys to register pthread mutexes/cond in the current file with
performance schema */
static mysql_pfs_key_t innobase_share_mutex_key;
static mysql_pfs_key_t prepare_commit_mutex_key;
static mysql_pfs_key_t commit_threads_m_key;
static mysql_pfs_key_t commit_cond_mutex_key;
static mysql_pfs_key_t commit_cond_key;
static PSI_mutex_info all_pthread_mutexes[] = {
{&commit_threads_m_key, "commit_threads_m", 0},
{&commit_cond_mutex_key, "commit_cond_mutex", 0},
{&innobase_share_mutex_key, "innobase_share_mutex", 0},
{&prepare_commit_mutex_key, "prepare_commit_mutex", 0}
};
static PSI_cond_info all_innodb_conds[] = {
{&commit_cond_key, "commit_cond", 0}
};
# ifdef UNIV_PFS_MUTEX
/* all_innodb_mutexes array contains mutexes that are
performance schema instrumented if "UNIV_PFS_MUTEX"
is defined */
static PSI_mutex_info all_innodb_mutexes[] = {
{&autoinc_mutex_key, "autoinc_mutex", 0},
{&btr_search_enabled_mutex_key, "btr_search_enabled_mutex", 0},
# ifndef PFS_SKIP_BUFFER_MUTEX_RWLOCK
{&buffer_block_mutex_key, "buffer_block_mutex", 0},
# endif /* !PFS_SKIP_BUFFER_MUTEX_RWLOCK */
{&buf_pool_mutex_key, "buf_pool_mutex", 0},
{&buf_pool_zip_mutex_key, "buf_pool_zip_mutex", 0},
{&cache_last_read_mutex_key, "cache_last_read_mutex", 0},
{&dict_foreign_err_mutex_key, "dict_foreign_err_mutex", 0},
{&dict_sys_mutex_key, "dict_sys_mutex", 0},
{&file_format_max_mutex_key, "file_format_max_mutex", 0},
{&fil_system_mutex_key, "fil_system_mutex", 0},
{&flush_list_mutex_key, "flush_list_mutex", 0},
{&log_flush_order_mutex_key, "log_flush_order_mutex", 0},
{&hash_table_mutex_key, "hash_table_mutex", 0},
{&ibuf_bitmap_mutex_key, "ibuf_bitmap_mutex", 0},
{&ibuf_mutex_key, "ibuf_mutex", 0},
{&ibuf_pessimistic_insert_mutex_key,
"ibuf_pessimistic_insert_mutex", 0},
{&kernel_mutex_key, "kernel_mutex", 0},
{&log_sys_mutex_key, "log_sys_mutex", 0},
# ifdef UNIV_MEM_DEBUG
{&mem_hash_mutex_key, "mem_hash_mutex", 0},
# endif /* UNIV_MEM_DEBUG */
{&mem_pool_mutex_key, "mem_pool_mutex", 0},
{&mutex_list_mutex_key, "mutex_list_mutex", 0},
{&purge_sys_bh_mutex_key, "purge_sys_bh_mutex", 0},
{&recv_sys_mutex_key, "recv_sys_mutex", 0},
{&rseg_mutex_key, "rseg_mutex", 0},
# ifdef UNIV_SYNC_DEBUG
{&rw_lock_debug_mutex_key, "rw_lock_debug_mutex", 0},
# endif /* UNIV_SYNC_DEBUG */
{&rw_lock_list_mutex_key, "rw_lock_list_mutex", 0},
{&rw_lock_mutex_key, "rw_lock_mutex", 0},
{&srv_dict_tmpfile_mutex_key, "srv_dict_tmpfile_mutex", 0},
{&srv_innodb_monitor_mutex_key, "srv_innodb_monitor_mutex", 0},
{&srv_misc_tmpfile_mutex_key, "srv_misc_tmpfile_mutex", 0},
{&srv_monitor_file_mutex_key, "srv_monitor_file_mutex", 0},
{&syn_arr_mutex_key, "syn_arr_mutex", 0},
# ifdef UNIV_SYNC_DEBUG
{&sync_thread_mutex_key, "sync_thread_mutex", 0},
# endif /* UNIV_SYNC_DEBUG */
{&trx_doublewrite_mutex_key, "trx_doublewrite_mutex", 0},
{&trx_undo_mutex_key, "trx_undo_mutex", 0}
};
# endif /* UNIV_PFS_MUTEX */
# ifdef UNIV_PFS_RWLOCK
/* 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 UNIV_LOG_ARCHIVE
{&archive_lock_key, "archive_lock", 0},
# endif /* UNIV_LOG_ARCHIVE */
{&btr_search_latch_key, "btr_search_latch", 0},
# ifndef PFS_SKIP_BUFFER_MUTEX_RWLOCK
{&buf_block_lock_key, "buf_block_lock", 0},
# endif /* !PFS_SKIP_BUFFER_MUTEX_RWLOCK */
# ifdef UNIV_SYNC_DEBUG
{&buf_block_debug_latch_key, "buf_block_debug_latch", 0},
# endif /* UNIV_SYNC_DEBUG */
{&dict_operation_lock_key, "dict_operation_lock", 0},
{&fil_space_latch_key, "fil_space_latch", 0},
{&checkpoint_lock_key, "checkpoint_lock", 0},
{&trx_i_s_cache_lock_key, "trx_i_s_cache_lock", 0},
{&trx_purge_latch_key, "trx_purge_latch", 0},
{&index_tree_rw_lock_key, "index_tree_rw_lock", 0},
{&dict_table_stats_latch_key, "dict_table_stats", 0}
};
# 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[] = {
{&trx_rollback_clean_thread_key, "trx_rollback_clean_thread", 0},
{&io_handler_thread_key, "io_handler_thread", 0},
{&srv_lock_timeout_thread_key, "srv_lock_timeout_thread", 0},
{&srv_error_monitor_thread_key, "srv_error_monitor_thread", 0},
{&srv_monitor_thread_key, "srv_monitor_thread", 0},
{&srv_master_thread_key, "srv_master_thread", 0},
{&srv_purge_thread_key, "srv_purge_thread", 0}
};
# 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[] = {
{&innodb_file_data_key, "innodb_data_file", 0},
{&innodb_file_log_key, "innodb_log_file", 0},
{&innodb_file_temp_key, "innodb_temp_file", 0}
};
# endif /* UNIV_PFS_IO */
#endif /* HAVE_PSI_INTERFACE */
static INNOBASE_SHARE *get_share(const char *table_name);
static void free_share(INNOBASE_SHARE *share);
static int innobase_close_connection(handlerton *hton, THD* thd);
static void innobase_kill_connection(handlerton *hton, THD* thd);
static int innobase_commit(handlerton *hton, THD* thd, bool all);
static int innobase_rollback(handlerton *hton, THD* thd, bool all);
static int innobase_rollback_to_savepoint(handlerton *hton, THD* thd,
void *savepoint);
static int innobase_savepoint(handlerton *hton, THD* thd, void *savepoint);
static int innobase_release_savepoint(handlerton *hton, THD* thd,
void *savepoint);
static handler *innobase_create_handler(handlerton *hton,
TABLE_SHARE *table,
MEM_ROOT *mem_root);
/* "GEN_CLUST_INDEX" is the name reserved for Innodb default
system primary index. */
static const char innobase_index_reserve_name[]= "GEN_CLUST_INDEX";
/** @brief Initialize the default value of innodb_commit_concurrency.
Once InnoDB is running, the innodb_commit_concurrency must not change
from zero to nonzero. (Bug #42101)
The initial default value is 0, and without this extra initialization,
SET GLOBAL innodb_commit_concurrency=DEFAULT would set the parameter
to 0, even if it was initially set to nonzero at the command line
or configuration file. */
static
void
innobase_commit_concurrency_init_default(void);
/*==========================================*/
/************************************************************//**
Validate the file format name and return its corresponding id.
@return valid file format id */
static
uint
innobase_file_format_name_lookup(
/*=============================*/
const char* format_name); /*!< in: pointer to file format
name */
/************************************************************//**
Validate the file format check config parameters, as a side effect it
sets the srv_max_file_format_at_startup variable.
@return the format_id if valid config value, otherwise, return -1 */
static
int
innobase_file_format_validate_and_set(
/*==================================*/
const char* format_max); /*!< in: parameter value */
/****************************************************************//**
Return alter table flags supported in an InnoDB database. */
static
uint
innobase_alter_table_flags(
/*=======================*/
uint flags);
/****************************************************************//**
Manipulate internal parameters of the storage engine. */
static
longlong
innobase_control(
/*=============*/
handlerton* hton,
const char* cmd,
Item** args,
uint args_count);
static const char innobase_hton_name[]= "InnoDB";
/*************************************************************//**
Check for a valid value of innobase_commit_concurrency.
@return 0 for valid innodb_commit_concurrency */
static
int
innobase_commit_concurrency_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 */
{
long long intbuf;
ulong commit_concurrency;
DBUG_ENTER("innobase_commit_concurrency_validate");
if (value->val_int(value, &intbuf)) {
/* The value is NULL. That is invalid. */
DBUG_RETURN(1);
}
*reinterpret_cast<ulong*>(save) = commit_concurrency
= static_cast<ulong>(intbuf);
/* Allow the value to be updated, as long as it remains zero
or nonzero. */
DBUG_RETURN(!(!commit_concurrency == !innobase_commit_concurrency));
}
/****************************************************************//**
Expand and update the system variable index_page_split using the
"saved" value. */
static
void
innodb_index_page_split_mode_update(
/*================================*/
THD* thd, /*!< in: thread handle */
struct st_mysql_sys_var* var, /*!< in: pointer to
system variable */
void* var_ptr, /*!< out: where the
formal value goes */
const void* save) /*!< in: immediate result
from check function */
{
ulonglong flags;
flags = *(ulonglong *)(save);
if (flags & (BTR_PAGE_SPLIT_UPPER_FLAG | BTR_PAGE_SPLIT_LOWER_FLAG)) {
flags |= BTR_PAGE_SPLIT_SYMMETRIC_FLAG;
}
*(ulonglong *)(var_ptr) = flags;
}
static MYSQL_THDVAR_BOOL(support_xa, PLUGIN_VAR_OPCMDARG,
"Enable InnoDB support for the XA two-phase commit",
/* check_func */ NULL, /* update_func */ NULL,
/* default */ TRUE);
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, FALSE);
static MYSQL_THDVAR_ULONG(lock_wait_timeout, PLUGIN_VAR_RQCMDARG,
"Timeout in seconds an InnoDB transaction may wait for a lock before being rolled back. Values above 100000000 disable the timeout.",
NULL, NULL, 50, 1, 1024 * 1024 * 1024, 0);
static MYSQL_THDVAR_SET(index_page_split_mode, PLUGIN_VAR_RQCMDARG,
"Index page split behavior.", NULL, innodb_index_page_split_mode_update,
0, &innodb_index_page_split_mode_typelib);
static handler *innobase_create_handler(handlerton *hton,
TABLE_SHARE *table,
MEM_ROOT *mem_root)
{
return new (mem_root) ha_innobase(hton, table);
}
/*******************************************************************//**
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 - commit 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,
XID* xid); /*!< in: X/Open XA transaction identification */
/*******************************************************************//**
This function is used to rollback one X/Open XA distributed transaction
which is in the prepared state
@return 0 or error number */
static
int
innobase_rollback_by_xid(
/*=====================*/
handlerton* hton, /*!< in: InnoDB handlerton */
XID* xid); /*!< in: X/Open XA transaction
identification */
/*******************************************************************//**
Create a consistent view for a cursor based on current transaction
which is created if the corresponding MySQL thread still lacks one.
This consistent view is then used inside of MySQL when accessing records
using a cursor.
@return pointer to cursor view or NULL */
static
void*
innobase_create_cursor_view(
/*========================*/
handlerton* hton, /*!< in: innobase hton */
THD* thd); /*!< in: user thread handle */
/*******************************************************************//**
Set the given consistent cursor view to a transaction which is created
if the corresponding MySQL thread still lacks one. If the given
consistent cursor view is NULL global read view of a transaction is
restored to a transaction read view. */
static
void
innobase_set_cursor_view(
/*=====================*/
handlerton* hton,
THD* thd, /*!< in: user thread handle */
void* curview);/*!< in: Consistent cursor view to be set */
/*******************************************************************//**
Close the given consistent cursor view of a transaction and restore
global read view to a transaction read view. Transaction is created if the
corresponding MySQL thread still lacks one. */
static
void
innobase_close_cursor_view(
/*=======================*/
handlerton* hton,
THD* thd, /*!< in: user thread handle */
void* curview);/*!< in: Consistent read view to be closed */
/*****************************************************************//**
Removes all tables in the named database inside InnoDB. */
static
void
innobase_drop_database(
/*===================*/
handlerton* hton, /*!< in: handlerton of Innodb */
char* path); /*!< in: database path; inside InnoDB the name
of the last directory in the path is used as
the database name: for example, in 'mysql/data/test'
the database name is 'test' */
/*******************************************************************//**
Closes an InnoDB database. */
static
int
innobase_end(handlerton *hton, ha_panic_function type);
/*****************************************************************//**
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(
/*====================================*/
/* out: 0 */
handlerton* hton, /* in: Innodb handlerton */
THD* thd); /* in: MySQL thread handle of the user for whom
the transaction should be committed */
/****************************************************************//**
Flushes InnoDB logs to disk and makes a checkpoint. Really, a commit flushes
the logs, and the name of this function should be innobase_checkpoint.
@return TRUE if error */
static
bool
innobase_flush_logs(
/*================*/
handlerton* hton); /*!< in: InnoDB handlerton */
/************************************************************************//**
Implements the SHOW INNODB STATUS command. Sends the output of the InnoDB
Monitor to the client. */
static
bool
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
bool innobase_show_status(handlerton *hton, THD* thd,
stat_print_fn* stat_print,
enum ha_stat_type stat_type);
/*****************************************************************//**
Commits a transaction in an InnoDB database. */
static
void
innobase_commit_low(
/*================*/
trx_t* trx); /*!< in: transaction handle */
static SHOW_VAR innodb_status_variables[]= {
{"buffer_pool_LRU_search_scanned",
(char*) &export_vars.innodb_buffer_pool_LRU_search_scanned, SHOW_LONG},
{"buffer_pool_LRU_unzip_search_scanned",
(char*) &export_vars.innodb_buffer_pool_LRU_unzip_search_scanned, SHOW_LONG},
{"buffer_pool_LRU_get_free_search",
(char*) &export_vars.innodb_buffer_pool_LRU_get_free_search, SHOW_LONG},
{"buffer_pool_flush_LRU_batch_scanned",
(char*) &export_vars.innodb_buffer_pool_flush_LRU_batch_scanned, SHOW_LONG},
{"buffer_pool_flush_LRU_page_count",
(char*) &export_vars.innodb_buffer_pool_flush_LRU_page_count, SHOW_LONG},
{"buffer_pool_flush_adaptive_pages",
(char*) &export_vars.innodb_buffer_pool_flush_adaptive_pages, SHOW_LONG},
{"buffer_pool_flush_anticipatory_pages",
(char*) &export_vars.innodb_buffer_pool_flush_anticipatory_pages, SHOW_LONG},
{"buffer_pool_flush_async_page",
(char*) &export_vars.innodb_buffer_pool_flush_async_page, SHOW_LONG},
{"buffer_pool_flush_background_pages",
(char*) &export_vars.innodb_buffer_pool_flush_background_pages, SHOW_LONG},
{"buffer_pool_flush_batch_scanned",
(char*) &export_vars.innodb_buffer_pool_flush_batch_scanned, SHOW_LONG},
{"buffer_pool_flush_max_dirty_pages",
(char*) &export_vars.innodb_buffer_pool_flush_max_dirty_pages, SHOW_LONG},
{"buffer_pool_flush_neighbor_pages",
(char*) &export_vars.innodb_buffer_pool_flush_neighbor_pages, SHOW_LONG},
{"buffer_pool_flush_sync_page",
(char*) &export_vars.innodb_buffer_pool_flush_sync_page, SHOW_LONG},
{"buffer_pool_pages_data",
(char*) &export_vars.innodb_buffer_pool_pages_data, SHOW_LONG},
{"buffer_pool_pages_dirty",
(char*) &export_vars.innodb_buffer_pool_pages_dirty, SHOW_LONG},
{"buffer_pool_pages_flushed",
(char*) &export_vars.innodb_buffer_pool_pages_flushed, SHOW_LONG},
{"buffer_pool_pages_free",
(char*) &export_vars.innodb_buffer_pool_pages_free, SHOW_LONG},
#ifdef UNIV_DEBUG
{"buffer_pool_pages_latched",
(char*) &export_vars.innodb_buffer_pool_pages_latched, SHOW_LONG},
#endif /* UNIV_DEBUG */
{"buffer_pool_pages_misc",
(char*) &export_vars.innodb_buffer_pool_pages_misc, SHOW_LONG},
{"buffer_pool_pages_total",
(char*) &export_vars.innodb_buffer_pool_pages_total, SHOW_LONG},
{"buffer_pool_read_ahead_rnd",
(char*) &export_vars.innodb_buffer_pool_read_ahead_rnd, SHOW_LONG},
{"buffer_pool_read_ahead",
(char*) &export_vars.innodb_buffer_pool_read_ahead, SHOW_LONG},
{"buffer_pool_read_ahead_evicted",
(char*) &export_vars.innodb_buffer_pool_read_ahead_evicted, SHOW_LONG},
{"buffer_pool_read_requests",
(char*) &export_vars.innodb_buffer_pool_read_requests, SHOW_LONG},
{"buffer_pool_reads",
(char*) &export_vars.innodb_buffer_pool_reads, SHOW_LONG},
{"buffer_pool_wait_free",
(char*) &export_vars.innodb_buffer_pool_wait_free, SHOW_LONG},
{"buffer_pool_write_requests",
(char*) &export_vars.innodb_buffer_pool_write_requests, SHOW_LONG},
{"corrupted_page_reads",
(char*) &export_vars.innodb_corrupted_page_reads, SHOW_LONG},
{"corrupted_table_opens",
(char*) &export_vars.innodb_corrupted_table_opens, SHOW_LONG},
{"data_fsyncs",
(char*) &export_vars.innodb_data_fsyncs, SHOW_LONG},
{"data_pending_fsyncs",
(char*) &export_vars.innodb_data_pending_fsyncs, SHOW_LONG},
{"data_pending_reads",
(char*) &export_vars.innodb_data_pending_reads, SHOW_LONG},
{"data_pending_writes",
(char*) &export_vars.innodb_data_pending_writes, SHOW_LONG},
{"data_read",
(char*) &export_vars.innodb_data_read, SHOW_LONG},
{"data_reads",
(char*) &export_vars.innodb_data_reads, SHOW_LONG},
{"data_writes",
(char*) &export_vars.innodb_data_writes, SHOW_LONG},
{"data_written",
(char*) &export_vars.innodb_data_written, SHOW_LONG},
{"dblwr_pages_written",
(char*) &export_vars.innodb_dblwr_pages_written, SHOW_LONG},
{"dblwr_writes",
(char*) &export_vars.innodb_dblwr_writes, SHOW_LONG},
{"files_open",
(char*) &export_vars.innodb_files_open, SHOW_LONG},
{"files_opened",
(char*) &export_vars.innodb_files_opened, SHOW_LONG},
{"files_closed",
(char*) &export_vars.innodb_files_closed, SHOW_LONG},
{"files_flushed",
(char*) &export_vars.innodb_files_flushed, SHOW_LONG},
{"have_atomic_builtins",
(char*) &export_vars.innodb_have_atomic_builtins, SHOW_BOOL},
{"ibuf_discarded_delete_marks",
(char*) &export_vars.innodb_ibuf_discarded_delete_marks,SHOW_LONG},
{"ibuf_discarded_deletes",
(char*) &export_vars.innodb_ibuf_discarded_deletes, SHOW_LONG},
{"ibuf_discarded_inserts",
(char*) &export_vars.innodb_ibuf_discarded_inserts, SHOW_LONG},
{"ibuf_merged_delete_marks",
(char*) &export_vars.innodb_ibuf_merged_delete_marks, SHOW_LONG},
{"ibuf_merged_deletes",
(char*) &export_vars.innodb_ibuf_merged_deletes, SHOW_LONG},
{"ibuf_merged_inserts",
(char*) &export_vars.innodb_ibuf_merged_inserts, SHOW_LONG},
{"ibuf_merged_pages",
(char*) &export_vars.innodb_ibuf_merged_pages, SHOW_LONG},
{"ibuf_pages",
(char*) &export_vars.innodb_ibuf_pages, SHOW_LONG},
{"lock_deadlocks",
(char*) &export_vars.innodb_lock_deadlocks, SHOW_LONG},
{"log_waits",
(char*) &export_vars.innodb_log_waits, SHOW_LONG},
{"log_write_requests",
(char*) &export_vars.innodb_log_write_requests, SHOW_LONG},
{"log_writes",
(char*) &export_vars.innodb_log_writes, SHOW_LONG},
{"lsn_current",
(char*) &export_vars.innodb_lsn_current, SHOW_LONGLONG},
{"lsn_flushed",
(char*) &export_vars.innodb_lsn_flushed, SHOW_LONGLONG},
{"lsn_checkpoint",
(char*) &export_vars.innodb_lsn_checkpoint, SHOW_LONGLONG},
{"mysql_master_log_file",
(char*) export_vars.innodb_mysql_master_log_name, SHOW_CHAR},
{"mysql_master_log_pos",
(char*) &export_vars.innodb_mysql_master_log_pos, SHOW_LONGLONG},
{"os_log_fsyncs",
(char*) &export_vars.innodb_os_log_fsyncs, SHOW_LONG},
{"os_log_pending_fsyncs",
(char*) &export_vars.innodb_os_log_pending_fsyncs, SHOW_LONG},
{"os_log_pending_writes",
(char*) &export_vars.innodb_os_log_pending_writes, SHOW_LONG},
{"os_log_written",
(char*) &export_vars.innodb_os_log_written, SHOW_LONG},
{"page_discard",
(char*) &export_vars.innodb_btree_page_discard, SHOW_LONG},
{"page_merges",
(char*) &export_vars.innodb_btree_page_merge, SHOW_LONG},
{"page_merges_succeeded",
(char*) &export_vars.innodb_btree_page_merge_succ, SHOW_LONG},
{"page_reorganizes",
(char*) &export_vars.innodb_btree_page_reorganize, SHOW_LONG},
{"page_size",
(char*) &export_vars.innodb_page_size, SHOW_LONG},
{"page_splits",
(char*) &export_vars.innodb_btree_page_split, SHOW_LONG},
{"pages_created",
(char*) &export_vars.innodb_pages_created, SHOW_LONG},
{"pages_read",
(char*) &export_vars.innodb_pages_read, SHOW_LONG},
{"pages_written",
(char*) &export_vars.innodb_pages_written, SHOW_LONG},
{"purge_trx_no",
(char*) &export_vars.innodb_purge_trx_no, SHOW_LONGLONG},
{"purge_undo_no",
(char*) &export_vars.innodb_purge_undo_no, SHOW_LONGLONG},
{"row_lock_current_waits",
(char*) &export_vars.innodb_row_lock_current_waits, SHOW_LONG},
{"row_lock_time",
(char*) &export_vars.innodb_row_lock_time, SHOW_LONGLONG},
{"row_lock_time_avg",
(char*) &export_vars.innodb_row_lock_time_avg, SHOW_LONG},
{"row_lock_time_max",
(char*) &export_vars.innodb_row_lock_time_max, SHOW_LONG},
{"row_lock_waits",
(char*) &export_vars.innodb_row_lock_waits, SHOW_LONG},
{"rows_deleted",
(char*) &export_vars.innodb_rows_deleted, SHOW_LONG},
{"rows_inserted",
(char*) &export_vars.innodb_rows_inserted, SHOW_LONG},
{"rows_read",
(char*) &export_vars.innodb_rows_read, SHOW_LONG},
{"rows_updated",
(char*) &export_vars.innodb_rows_updated, SHOW_LONG},
{"tablespace_files_open",
(char*) &export_vars.innodb_tablespace_files_open, SHOW_LONG},
{"tablespace_files_opened",
(char*) &export_vars.innodb_tablespace_files_opened, SHOW_LONG},
{"tablespace_files_closed",
(char*) &export_vars.innodb_tablespace_files_closed, SHOW_LONG},
{"truncated_status_writes",
(char*) &export_vars.innodb_truncated_status_writes, SHOW_LONG},
{"trx_max_id",
(char*) &export_vars.innodb_trx_max_id, SHOW_LONGLONG},
{NullS, NullS, SHOW_LONG}
};
/* General functions */
/******************************************************************//**
Returns true if the thread is the replication thread on the slave
server. Used in srv_conc_enter_innodb() to determine if the thread
should be allowed to enter InnoDB - the replication thread is treated
differently than other threads. Also used in
srv_conc_force_exit_innodb().
@return true if thd is the replication thread */
extern "C" UNIV_INTERN
ibool
thd_is_replication_slave_thread(
/*============================*/
void* thd) /*!< in: thread handle (THD*) */
{
return((ibool) thd_slave_thread((THD*) thd));
}
/******************************************************************//**
Save some CPU by testing the value of srv_thread_concurrency in inline
functions. */
static inline
void
innodb_srv_conc_enter_innodb(
/*=========================*/
trx_t* trx) /*!< in: transaction handle */
{
if (UNIV_LIKELY(!srv_thread_concurrency)) {
return;
}
srv_conc_enter_innodb(trx);
}
/******************************************************************//**
Save some CPU by testing the value of srv_thread_concurrency in inline
functions. */
static inline
void
innodb_srv_conc_exit_innodb(
/*========================*/
trx_t* trx) /*!< in: transaction handle */
{
if (UNIV_LIKELY(!trx->declared_to_be_inside_innodb)) {
return;
}
srv_conc_exit_innodb(trx);
}
/******************************************************************//**
Releases possible search latch and InnoDB thread FIFO ticket. These should
be released at each SQL statement end, and also when mysqld passes the
control to the client. It does no harm to release these also in the middle
of an SQL statement. */
static inline
void
innobase_release_stat_resources(
/*============================*/
trx_t* trx) /*!< in: transaction object */
{
if (trx->has_search_latch) {
trx_search_latch_release_if_reserved(trx);
}
if (trx->declared_to_be_inside_innodb) {
/* Release our possible ticket in the FIFO */
srv_conc_force_exit_innodb(trx);
}
}
/******************************************************************//**
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 */
extern "C" UNIV_INTERN
ibool
thd_has_edited_nontrans_tables(
/*===========================*/
void* thd) /*!< in: thread handle (THD*) */
{
return((ibool) thd_non_transactional_update((THD*) thd));
}
/******************************************************************//**
Returns true if the thread is executing a SELECT statement.
@return true if thd is executing SELECT */
extern "C" UNIV_INTERN
ibool
thd_is_select(
/*==========*/
const void* thd) /*!< in: thread handle (THD*) */
{
return(thd_sql_command((const THD*) thd) == SQLCOM_SELECT);
}
/******************************************************************//**
Returns true if the thread supports XA,
global value of innodb_supports_xa if thd is NULL.
@return true if thd has XA support */
extern "C" UNIV_INTERN
ibool
thd_supports_xa(
/*============*/
void* thd) /*!< in: thread handle (THD*), or NULL to query
the global innodb_supports_xa */
{
return(THDVAR((THD*) thd, support_xa));
}
/******************************************************************//**
Returns the lock wait timeout for the current connection.
@return the lock wait timeout, in seconds */
extern "C" UNIV_INTERN
ulong
thd_lock_wait_timeout(
/*==================*/
void* thd) /*!< in: thread handle (THD*), 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*) thd, lock_wait_timeout));
}
/******************************************************************//**
Get the set of flags specified in innodb_index_page_split_mode.
@return set of flags that are set */
extern "C" UNIV_INTERN
ulonglong
thd_index_page_split(
/*=================*/
void* thd) /*!< in: thread handle (THD*), or NULL to query
the global innodb_index_page_split_mode */
{
/* According to <mysql/plugin.h>, passing thd == NULL
returns the global value of the session variable. */
return(THDVAR((THD*) thd, index_page_split_mode));
}
/******************************************************************//**
Set the time waited for the lock for the current query. */
extern "C" UNIV_INTERN
void
thd_set_lock_wait_time(
/*===================*/
void* thd, /*!< in: thread handle (THD*) */
ulint value) /*!< in: time waited for the lock */
{
if (thd) {
thd_storage_lock_wait((THD*)thd, value);
}
}
/********************************************************************//**
Obtain the InnoDB transaction of a MySQL thread.
@return reference to transaction pointer */
static inline
trx_t*&
thd_to_trx(
/*=======*/
THD* thd) /*!< in: MySQL thread */
{
return(*(trx_t**) thd_ha_data(thd, innodb_hton_ptr));
}
/********************************************************************//**
Call this function when mysqld passes control to the client. That is to
avoid deadlocks on the adaptive hash S-latch possibly held by thd. For more
documentation, see handler.cc.
@return 0 */
static
int
innobase_release_temporary_latches(
/*===============================*/
handlerton* hton, /*!< in: handlerton */
THD* thd) /*!< in: MySQL thread */
{
trx_t* trx;
DBUG_ASSERT(hton == innodb_hton_ptr);
if (!innodb_inited) {
return(0);
}
trx = thd_to_trx(thd);
if (trx) {
innobase_release_stat_resources(trx);
}
return(0);
}
/********************************************************************//**
Increments innobase_active_counter and every INNOBASE_WAKE_INTERVALth
time calls srv_active_wake_master_thread. This function should be used
when a single database operation may introduce a small need for
server utility activity, like checkpointing. */
static inline
void
innobase_active_small(void)
/*=======================*/
{
innobase_active_counter++;
if ((innobase_active_counter % INNOBASE_WAKE_INTERVAL) == 0) {
srv_active_wake_master_thread();
}
}
/********************************************************************//**
Set a detailed error message given an InnoDB error code. */
static inline
void
set_detailed_error(
/*================*/
int error, /*!< in: InnoDB error code */
THD* thd) /*!< in: user thread handle or NULL */
{
trx_t* trx;
const char* str;
trx = thd_to_trx(thd ? thd : thd_get_current_thd());
str = ut_get_strerr((enum db_err) error);
if (trx) {
trx_set_detailed_error(trx, str ? str : "");
}
}
/********************************************************************//**
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 */
extern "C" UNIV_INTERN
int
convert_error_code_to_mysql(
/*========================*/
int 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_FOREIGN_EXCEED_MAX_CASCADE:
push_warning_printf(thd, MYSQL_ERROR::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", DICT_FK_MAX_RECURSIVE_LOAD);
/* fall through */
case DB_ERROR:
default:
set_detailed_error(error, thd);
return(-1); /* unspecified error */
case DB_INTERRUPTED:
return(HA_ERR_INTERRUPTED);
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_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) {
thd_mark_transaction_to_rollback(thd, TRUE);
}
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, (bool)row_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_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:
return(HA_ERR_CRASHED);
case DB_OUT_OF_FILE_SPACE:
return(HA_ERR_RECORD_FILE_FULL);
case DB_TABLE_IN_FK_CHECK:
return(HA_ERR_TABLE_IN_FK_CHECK);
case DB_TABLE_IS_BEING_USED:
return(HA_ERR_WRONG_COMMAND);
case DB_TABLE_NOT_FOUND:
return(HA_ERR_NO_SUCH_TABLE);
case DB_TOO_BIG_RECORD:
my_error(ER_TOO_BIG_ROWSIZE, MYF(0),
page_get_free_space_of_empty(flags
& DICT_TF_COMPACT) / 2);
return(HA_ERR_TO_BIG_ROW);
case DB_TOO_BIG_INDEX_COL:
my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0),
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, TRUE);
}
return(HA_ERR_LOCK_TABLE_FULL);
case DB_PRIMARY_KEY_IS_NULL:
return(ER_PRIMARY_CANT_HAVE_NULL);
case DB_TOO_MANY_CONCURRENT_TRXS:
/* New error code HA_ERR_TOO_MANY_CONCURRENT_TRXS is only
available in 5.1.38 and later, but the plugin should still
work with previous versions of MySQL. */
#ifdef HA_ERR_TOO_MANY_CONCURRENT_TRXS
return(HA_ERR_TOO_MANY_CONCURRENT_TRXS);
#else /* HA_ERR_TOO_MANY_CONCURRENT_TRXS */
return(HA_ERR_RECORD_FILE_FULL);
#endif /* 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);
}
}
/*************************************************************//**
Prints info of a THD object (== user session thread) to the given file. */
extern "C" UNIV_INTERN
void
innobase_mysql_print_thd(
/*=====================*/
FILE* f, /*!< in: output stream */
void* thd, /*!< in: pointer to a 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_security_context((THD*) thd, buffer, sizeof buffer,
max_query_len), f);
putc('\n', f);
}
/******************************************************************//**
Get the variable length bounds of the given character set. */
extern "C" UNIV_INTERN
void
innobase_get_cset_width(
/*====================*/
ulint cset, /*!< in: MySQL charset-collation code */
ulint* mbminlen, /*!< out: minimum length of a char (in bytes) */
ulint* mbmaxlen) /*!< out: maximum length of a char (in bytes) */
{
CHARSET_INFO* cs;
ut_ad(cset < 256);
ut_ad(mbminlen);
ut_ad(mbmaxlen);
cs = all_charsets[cset];
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 ((global_system_variables.log_warnings)
&& (cset != 0)){
sql_print_warning(
"Unknown collation #%lu.", cset);
}
} else {
ut_a(cset == 0);
}
*mbminlen = *mbmaxlen = 0;
}
}
/******************************************************************//**
Converts an identifier to a table name. */
extern "C" UNIV_INTERN
void
innobase_convert_from_table_id(
/*===========================*/
struct charset_info_st* 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, &my_charset_filename, to, (uint) len, &errors);
}
/******************************************************************//**
Converts an identifier to UTF-8. */
extern "C" UNIV_INTERN
void
innobase_convert_from_id(
/*=====================*/
struct charset_info_st* 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, 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 */
extern "C" UNIV_INTERN
int
innobase_strcasecmp(
/*================*/
const char* a, /*!< in: first string to compare */
const char* b) /*!< in: second string to compare */
{
return(my_strcasecmp(system_charset_info, a, b));
}
/******************************************************************//**
Strip dir name from a full path name and return only the file name
@return file name or "null" if no file name */
extern "C" UNIV_INTERN
const char*
innobase_basename(
/*==============*/
const char* path_name) /*!< in: full 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. */
extern "C" UNIV_INTERN
void
innobase_casedn_str(
/*================*/
char* a) /*!< in/out: string to put in lower case */
{
my_casedn_str(system_charset_info, a);
}
/**********************************************************************//**
Determines the connection character set.
@return connection character set */
extern "C" UNIV_INTERN
struct charset_info_st*
innobase_get_charset(
/*=================*/
void* mysql_thd) /*!< in: MySQL thread handle */
{
return(thd_charset((THD*) mysql_thd));
}
/**********************************************************************//**
Determines the current SQL statement.
@return SQL statement string */
extern "C" UNIV_INTERN
const char*
innobase_get_stmt(
/*==============*/
void* mysql_thd, /*!< in: MySQL thread handle */
size_t* length) /*!< out: length of the SQL statement */
{
LEX_STRING* stmt;
stmt = thd_query_string((THD*) mysql_thd);
*length = stmt->length;
return(stmt->str);
}
/**********************************************************************//**
Get the current setting of the lower_case_table_names global parameter from
mysqld.cc. We do a dirty read because for one there is no synchronization
object and secondly there is little harm in doing so even if we get a torn
read.
@return value of lower_case_table_names */
extern "C" UNIV_INTERN
ulint
innobase_get_lower_case_table_names(void)
/*=====================================*/
{
return(lower_case_table_names);
}
#if defined (__WIN__) && defined (MYSQL_DYNAMIC_PLUGIN)
extern MYSQL_PLUGIN_IMPORT MY_TMPDIR mysql_tmpdir_list;
/*******************************************************************//**
Map an OS error to an errno value. The OS error number is stored in
_doserrno and the mapped value is stored in errno) */
extern "C"
void __cdecl
_dosmaperr(
unsigned long); /*!< in: OS error value */
/*********************************************************************//**
Creates a temporary file.
@return temporary file descriptor, or < 0 on error */
extern "C" UNIV_INTERN
int
innobase_mysql_tmpfile(void)
/*========================*/
{
int fd; /* handle of opened file */
HANDLE osfh; /* OS handle of opened file */
char* tmpdir; /* point to the directory
where to create file */
TCHAR path_buf[MAX_PATH - 14]; /* buffer for tmp file path.
The length cannot be longer
than MAX_PATH - 14, or
GetTempFileName will fail. */
char filename[MAX_PATH]; /* name of the tmpfile */
DWORD fileaccess = GENERIC_READ /* OS file access */
| GENERIC_WRITE
| DELETE;
DWORD fileshare = FILE_SHARE_READ /* OS file sharing mode */
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE;
DWORD filecreate = CREATE_ALWAYS; /* OS method of open/create */
DWORD fileattrib = /* OS file attribute flags */
FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_DELETE_ON_CLOSE
| FILE_ATTRIBUTE_TEMPORARY
| FILE_FLAG_SEQUENTIAL_SCAN;
DBUG_ENTER("innobase_mysql_tmpfile");
tmpdir = my_tmpdir(&mysql_tmpdir_list);
/* The tmpdir parameter can not be NULL for GetTempFileName. */
if (!tmpdir) {
uint ret;
/* Use GetTempPath to determine path for temporary files. */
ret = GetTempPath(sizeof(path_buf), path_buf);
if (ret > sizeof(path_buf) || (ret == 0)) {
_dosmaperr(GetLastError()); /* map error */
DBUG_RETURN(-1);
}
tmpdir = path_buf;
}
/* Use GetTempFileName to generate a unique filename. */
if (!GetTempFileName(tmpdir, "ib", 0, filename)) {
_dosmaperr(GetLastError()); /* map error */
DBUG_RETURN(-1);
}
DBUG_PRINT("info", ("filename: %s", filename));
/* Open/Create the file. */
osfh = CreateFile(filename, fileaccess, fileshare, NULL,
filecreate, fileattrib, NULL);
if (osfh == INVALID_HANDLE_VALUE) {
/* open/create file failed! */
_dosmaperr(GetLastError()); /* map error */
DBUG_RETURN(-1);
}
do {
/* Associates a CRT file descriptor with the OS file handle. */
fd = _open_osfhandle((intptr_t) osfh, 0);
} while (fd == -1 && errno == EINTR);
if (fd == -1) {
/* Open failed, close the file handle. */
_dosmaperr(GetLastError()); /* map error */
CloseHandle(osfh); /* no need to check if
CloseHandle fails */
}
DBUG_RETURN(fd);
}
#else
/*********************************************************************//**
Creates a temporary file.
@return temporary file descriptor, or < 0 on error */
extern "C" UNIV_INTERN
int
innobase_mysql_tmpfile(void)
/*========================*/
{
int fd2 = -1;
File fd = mysql_tmpfile("ib");
if (fd >= 0) {
/* Copy the file descriptor, so that the additional resources
allocated by create_temp_file() can be freed by invoking
my_close().
Because the file descriptor returned by this function
will be passed to fdopen(), it will be closed by invoking
fclose(), which in turn will invoke close() instead of
my_close(). */
#ifdef _WIN32
/* Note that on Windows, the integer returned by mysql_tmpfile
has no relation to C runtime file descriptor. Here, we need
to call my_get_osfhandle to get the HANDLE and then convert it
to C runtime filedescriptor. */
{
HANDLE hFile = my_get_osfhandle(fd);
HANDLE hDup;
BOOL bOK =
DuplicateHandle(GetCurrentProcess(), hFile, GetCurrentProcess(),
&hDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
if(bOK) {
fd2 = _open_osfhandle((intptr_t)hDup,0);
}
else {
my_osmaperr(GetLastError());
fd2 = -1;
}
}
#else
fd2 = dup(fd);
#endif
if (fd2 < 0) {
DBUG_PRINT("error",("Got error %d on dup",fd2));
my_errno=errno;
my_error(EE_OUT_OF_FILERESOURCES,
MYF(ME_BELL+ME_WAITTANG),
"ib*", my_errno);
}
my_close(fd, MYF(MY_WME));
}
return(fd2);
}
#endif /* defined (__WIN__) && defined (MYSQL_DYNAMIC_PLUGIN) */
/*********************************************************************//**
Wrapper around MySQL's copy_and_convert function.
@return number of bytes copied to 'to' */
extern "C" UNIV_INTERN
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 */
extern "C" UNIV_INTERN
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));
}
/*********************************************************************//**
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 */
static
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 = need * step;
/* Should never be 0. */
ut_a(need > 0);
ut_a(block > 0);
ut_a(max_value > 0);
/* Current value should never be greater than the maximum. */
ut_a(current <= max_value);
/* According to MySQL documentation, if the offset is greater than
the step then the offset is ignored. */
if (offset > block) {
offset = 0;
}
/* Check for overflow. */
if (block >= max_value
|| offset > max_value
|| current == max_value
|| max_value - offset <= offset) {
next_value = max_value;
} else {
ut_a(max_value > current);
ulonglong free = max_value - current;
if (free < offset || free - offset <= block) {
next_value = max_value;
} else {
next_value = 0;
}
}
if (next_value == 0) {
ulonglong next;
if (current > offset) {
next = (current - offset) / step;
} else {
next = (offset - current) / step;
}
ut_a(max_value > next);
next_value = next * step;
/* Check for multiplication overflow. */
ut_a(next_value >= next);
ut_a(max_value > next_value);
/* Check for overflow */
if (max_value - next_value >= block) {
next_value += block;
if (max_value - next_value >= offset) {
next_value += offset;
} else {
next_value = max_value;
}
} else {
next_value = max_value;
}
}
ut_a(next_value != 0);
ut_a(next_value <= max_value);
return(next_value);
}
/*********************************************************************//**
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(EQ_CURRENT_THD(thd));
DBUG_ASSERT(thd == trx->mysql_thd);
trx->check_foreigns = !thd_test_options(
thd, OPTION_NO_FOREIGN_KEY_CHECKS);
trx->check_unique_secondary = !thd_test_options(
thd, OPTION_RELAXED_UNIQUE_CHECKS);
DBUG_VOID_RETURN;
}
/*********************************************************************//**
Allocates an InnoDB transaction for a MySQL handler object.
@return InnoDB transaction handle */
extern "C" UNIV_INTERN
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_allocate_for_mysql();
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 */
{
trx_t*& trx = thd_to_trx(thd);
ut_ad(EQ_CURRENT_THD(thd));
if (trx == NULL) {
trx = innobase_trx_allocate(thd);
} else if (UNIV_UNLIKELY(trx->magic_n != TRX_MAGIC_N)) {
mem_analyze_corruption(trx);
ut_error;
}
innobase_trx_init(thd, trx);
return(trx);
}
/*********************************************************************//**
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 owns the prepare_commit_mutex. */
static inline
void
trx_owns_prepare_commit_mutex_set(
/*==============================*/
trx_t* trx) /* in: transaction */
{
ut_a(trx_is_registered_for_2pc(trx));
trx->owns_prepare_mutex = 1;
}
/*********************************************************************//**
Note that a transaction has been registered with MySQL 2PC coordinator. */
static inline
void
trx_register_for_2pc(
/*==================*/
trx_t* trx) /* in: transaction */
{
trx->is_registered = 1;
ut_ad(trx->owns_prepare_mutex == 0);
}
/*********************************************************************//**
Note that a transaction has been deregistered. */
static inline
void
trx_deregister_from_2pc(
/*====================*/
trx_t* trx) /* in: transaction */
{
trx->is_registered = 0;
trx->owns_prepare_mutex = 0;
}
/*********************************************************************//**
Check whether atransaction owns the prepare_commit_mutex.
@return true if transaction owns the prepare commit mutex */
static inline
bool
trx_has_prepare_commit_mutex(
/*=========================*/
const trx_t* trx) /* in: transaction */
{
return(trx->owns_prepare_mutex == 1);
}
/*********************************************************************//**
Check if transaction is started.
@reutrn true if transaction is in state started */
static
bool
trx_is_started(
/*===========*/
trx_t* trx) /* in: transaction */
{
return(trx->conc_state != TRX_NOT_STARTED);
}
/*********************************************************************//**
Construct ha_innobase handler. */
UNIV_INTERN
ha_innobase::ha_innobase(handlerton *hton, TABLE_SHARE *table_arg)
:handler(hton, table_arg),
int_table_flags(HA_REC_NOT_IN_SEQ |
HA_NULL_IN_KEY |
HA_CAN_INDEX_BLOBS |
HA_CAN_SQL_HANDLER |
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),
start_of_scan(0),
num_write_row(0)
{}
/*********************************************************************//**
Destruct ha_innobase handler. */
UNIV_INTERN
ha_innobase::~ha_innobase()
{
}
/*********************************************************************//**
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
prebuilt struct. */
UNIV_INTERN inline
void
ha_innobase::update_thd(
/*====================*/
THD* thd) /*!< in: thd to use the handle */
{
trx_t* trx;
trx = check_trx_exists(thd);
if (prebuilt->trx != trx) {
row_update_prebuilt_trx(prebuilt, trx);
}
user_thd = thd;
}
/*********************************************************************//**
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
prebuilt struct. */
UNIV_INTERN
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 */
{
trans_register_ha(thd, FALSE, hton);
if (!trx_is_registered_for_2pc(trx)
&& thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) {
trans_register_ha(thd, TRUE, hton);
}
trx_register_for_2pc(trx);
}
/* 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.
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.
*/
/******************************************************************//**
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 reserver the InnoDB kernel mutex.
Thus, the 'rank' in sync0sync.h of the MySQL query cache mutex is above
the InnoDB kernel 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 */
char* full_name, /*!< in: concatenation of database name,
the null character NUL, and the table
name */
uint full_name_len, /*!< in: length of the full name, i.e.
len(dbname) + len(tablename) + 1 */
ulonglong *unused) /*!< unused for this engine */
{
ibool is_autocommit;
trx_t* trx;
char norm_name[1000];
ut_a(full_name_len < 999);
trx = check_trx_exists(thd);
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((my_bool)FALSE);
}
if (trx->has_search_latch) {
sql_print_error("The calling thread is holding the adaptive "
"search, latch though calling "
"innobase_query_caching_of_table_permitted.");
mutex_enter(&kernel_mutex);
trx_print(stderr, trx, 1024);
mutex_exit(&kernel_mutex);
}
innobase_release_stat_resources(trx);
if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) {
is_autocommit = TRUE;
} else {
is_autocommit = FALSE;
}
if (is_autocommit && 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((my_bool)TRUE);
}
/* Normalize the table name to InnoDB format */
memcpy(norm_name, full_name, full_name_len);
norm_name[strlen(norm_name)] = '/'; /* InnoDB uses '/' as the
separator between db and table */
norm_name[full_name_len] = '\0';
#ifdef __WIN__
innobase_casedn_str(norm_name);
#endif
innobase_register_trx(innodb_hton_ptr, thd, trx);
if (row_search_check_if_query_cache_permitted(trx, norm_name)) {
/* printf("Query cache for %s permitted\n", norm_name); */
return((my_bool)TRUE);
}
/* printf("Query cache for %s NOT permitted\n", norm_name); */
return((my_bool)FALSE);
}
/*****************************************************************//**
Invalidates the MySQL query cache for the table. */
extern "C" UNIV_INTERN
void
innobase_invalidate_query_cache(
/*============================*/
trx_t* trx, /*!< in: transaction which
modifies the table */
const char* full_name, /*!< in: concatenation of
database name, null char NUL,
table name, null char NUL;
NOTE that in Windows this is
always in LOWER CASE! */
ulint full_name_len) /*!< in: full name length where
also the null chars count */
{
/* Note that the sync0sync.h rank of the query cache mutex is just
above the InnoDB kernel mutex. The caller of this function must not
have latches of a lower rank. */
/* Argument TRUE below means we are using transactions */
#ifdef HAVE_QUERY_CACHE
mysql_query_cache_invalidate4((THD*) trx->mysql_thd,
full_name,
(uint32) full_name_len,
TRUE);
#endif
}
/*****************************************************************//**
Convert an SQL identifier to the MySQL system_charset_info (UTF-8)
and quote it if needed.
@return pointer to the end of buf */
static
char*
innobase_convert_identifier(
/*========================*/
char* buf, /*!< out: buffer for converted identifier */
ulint buflen, /*!< in: length of buf, in bytes */
const char* id, /*!< in: identifier to convert */
ulint idlen, /*!< in: length of id, in bytes */
void* thd, /*!< in: MySQL connection thread, or NULL */
ibool file_id)/*!< in: TRUE=id is a table or database name;
FALSE=id is an UTF-8 string */
{
char nz[NAME_LEN + 1];
char nz2[NAME_LEN + 1 + EXPLAIN_FILENAME_MAX_EXTRA_LENGTH];
const char* s = id;
int q;
if (file_id) {
/* Decode the table name. The MySQL function expects
a NUL-terminated string. The input and output strings
buffers must not be shared. */
if (UNIV_UNLIKELY(idlen > (sizeof nz) - 1)) {
idlen = (sizeof nz) - 1;
}
memcpy(nz, id, idlen);
nz[idlen] = 0;
s = nz2;
idlen = explain_filename((THD*) thd, nz, nz2, sizeof nz2,
EXPLAIN_PARTITIONS_AS_COMMENT);
goto no_quote;
}
/* See if the identifier needs to be quoted. */
if (UNIV_UNLIKELY(!thd)) {
q = '"';
} else {
q = get_quote_char_for_identifier((THD*) thd, s, (int) idlen);
}
if (q == EOF) {
no_quote:
if (UNIV_UNLIKELY(idlen > buflen)) {
idlen = buflen;
}
memcpy(buf, s, idlen);
return(buf + idlen);
}
/* Quote the identifier. */
if (buflen < 2) {
return(buf);
}
*buf++ = q;
buflen--;
for (; idlen; idlen--) {
int c = *s++;
if (UNIV_UNLIKELY(c == q)) {
if (UNIV_UNLIKELY(buflen < 3)) {
break;
}
*buf++ = c;
*buf++ = c;
buflen -= 2;
} else {
if (UNIV_UNLIKELY(buflen < 2)) {
break;
}
*buf++ = c;
buflen--;
}
}
*buf++ = q;
return(buf);
}
/*****************************************************************//**
Convert a table or index name to the MySQL system_charset_info (UTF-8)
and quote it if needed.
@return pointer to the end of buf */
extern "C" UNIV_INTERN
char*
innobase_convert_name(
/*==================*/
char* buf, /*!< out: buffer for converted identifier */
ulint buflen, /*!< in: length of buf, in bytes */
const char* id, /*!< in: identifier to convert */
ulint idlen, /*!< in: length of id, in bytes */
void* thd, /*!< in: MySQL connection thread, or NULL */
ibool table_id)/*!< in: TRUE=id is a table or database name;
FALSE=id is an index name */
{
char* s = buf;
const char* bufend = buf + buflen;
if (table_id) {
const char* slash = (const char*) memchr(id, '/', idlen);
if (!slash) {
goto no_db_name;
}
/* Print the database name and table name separately. */
s = innobase_convert_identifier(s, bufend - s, id, slash - id,
thd, TRUE);
if (UNIV_LIKELY(s < bufend)) {
*s++ = '.';
s = innobase_convert_identifier(s, bufend - s,
slash + 1, idlen
- (slash - id) - 1,
thd, TRUE);
}
} else if (UNIV_UNLIKELY(*id == TEMP_INDEX_PREFIX)) {
/* Temporary index name (smart ALTER TABLE) */
const char temp_index_suffix[]= "--temporary--";
s = innobase_convert_identifier(buf, buflen, id + 1, idlen - 1,
thd, FALSE);
if (s - buf + (sizeof temp_index_suffix - 1) < buflen) {
memcpy(s, temp_index_suffix,
sizeof temp_index_suffix - 1);
s += sizeof temp_index_suffix - 1;
}
} else {
no_db_name:
s = innobase_convert_identifier(buf, buflen, id, idlen,
thd, table_id);
}
return(s);
}
/*****************************************************************//**
A wrapper function of innobase_convert_name(), convert a table or
index name to the MySQL system_charset_info (UTF-8) and quote it if needed.
@return pointer to the end of buf */
static inline
void
innobase_format_name(
/*==================*/
char* buf, /*!< out: buffer for converted identifier */
ulint buflen, /*!< in: length of buf, in bytes */
const char* name, /*!< in: index or table name to format */
ibool is_index_name) /*!< in: index name */
{
const char* bufend;
bufend = innobase_convert_name(buf, buflen, name, strlen(name),
NULL, !is_index_name);
ut_ad((ulint) (bufend - buf) < buflen);
buf[bufend - buf] = '\0';
}
/**********************************************************************//**
Determines if the currently running transaction has been interrupted.
@return TRUE if interrupted */
extern "C" UNIV_INTERN
ibool
trx_is_interrupted(
/*===============*/
trx_t* trx) /*!< in: transaction */
{
return(trx && trx->mysql_thd && thd_killed((THD*) trx->mysql_thd));
}
/**********************************************************************//**
Determines if the currently running transaction is in strict mode.
@return TRUE if strict */
extern "C" UNIV_INTERN
ibool
trx_is_strict(
/*==========*/
trx_t* trx) /*!< in: transaction */
{
return(trx && trx->mysql_thd
&& THDVAR((THD*) trx->mysql_thd, strict_mode));
}
/**************************************************************//**
Resets some fields of a prebuilt struct. The template is used in fast
retrieval of just those column values MySQL needs in its processing. */
static
void
reset_template(
/*===========*/
row_prebuilt_t* prebuilt) /*!< in/out: prebuilt struct */
{
prebuilt->keep_other_fields_on_keyread = 0;
prebuilt->read_just_key = 0;
}
/*****************************************************************//**
Call this when you have opened a new table handle in HANDLER, before you
call index_read_idx() 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. */
UNIV_INTERN
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 prebuilt struct, create
one. Update the trx pointers in the prebuilt struct. Normally
this operation is done in external_lock. */
update_thd(ha_thd());
/* Initialize the prebuilt struct much like it would be inited in
external_lock */
innobase_release_stat_resources(prebuilt->trx);
/* If the transaction is not started yet, start it */
trx_start_if_not_started(prebuilt->trx);
/* Assign a read view if the transaction does not have it yet */
trx_assign_read_view(prebuilt->trx);
innobase_register_trx(ht, user_thd, prebuilt->trx);
/* We did the necessary inits in this function, no need to repeat them
in row_search_for_mysql */
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 */
prebuilt->select_lock_type = LOCK_NONE;
prebuilt->stored_select_lock_type = LOCK_NONE;
/* Always fetch all columns in the index record */
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???? */
prebuilt->used_in_HANDLER = TRUE;
reset_template(prebuilt);
}
/*********************************************************************//**
Opens an InnoDB database.
@return 0 on success, error code on failure */
static
int
innobase_init(
/*==========*/
void *p) /*!< in: InnoDB handlerton */
{
static char current_dir[3]; /*!< Set if using current lib */
int err;
bool ret;
char *default_path;
uint format_id;
DBUG_ENTER("innobase_init");
handlerton *innobase_hton= (handlerton *)p;
innodb_hton_ptr = innobase_hton;
innobase_hton->state = SHOW_OPTION_YES;
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->savepoint_set=innobase_savepoint;
innobase_hton->savepoint_rollback=innobase_rollback_to_savepoint;
innobase_hton->savepoint_release=innobase_release_savepoint;
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->create_cursor_read_view=innobase_create_cursor_view;
innobase_hton->set_cursor_read_view=innobase_set_cursor_view;
innobase_hton->close_cursor_read_view=innobase_close_cursor_view;
innobase_hton->create=innobase_create_handler;
innobase_hton->drop_database=innobase_drop_database;
innobase_hton->panic=innobase_end;
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->flags=HTON_NO_FLAGS;
innobase_hton->release_temporary_latches=innobase_release_temporary_latches;
innobase_hton->alter_table_flags = innobase_alter_table_flags;
innobase_hton->control = innobase_control;
innobase_hton->kill_connection = innobase_kill_connection;
ut_a(DATA_MYSQL_TRUE_VARCHAR == (ulint)MYSQL_TYPE_VARCHAR);
#ifndef DBUG_OFF
static const char test_filename[] = "-@";
char test_tablename[sizeof test_filename
+ sizeof srv_mysql50_table_name_prefix];
if ((sizeof test_tablename) - 1
!= filename_to_tablename(test_filename, test_tablename,
sizeof test_tablename, true)
|| strncmp(test_tablename,
srv_mysql50_table_name_prefix,
sizeof srv_mysql50_table_name_prefix)
|| strcmp(test_tablename
+ sizeof srv_mysql50_table_name_prefix,
test_filename)) {
sql_print_error("tablename encoding has been changed");
goto error;
}
#endif /* DBUG_OFF */
/* Check that values don't overflow on 32-bit systems. */
if (sizeof(ulint) == 4) {
if (innobase_buffer_pool_size > UINT_MAX32) {
sql_print_error(
"innobase_buffer_pool_size can't be over 4GB"
" on 32-bit systems");
goto error;
}
if (innobase_log_file_size > UINT_MAX32) {
sql_print_error(
"innobase_log_file_size can't be over 4GB"
" on 32-bit systems");
goto error;
}
}
os_innodb_umask = (ulint)my_umask;
/* 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. */
if (mysqld_embedded) {
default_path = mysql_real_data_home;
fil_path_to_mysql_datadir = mysql_real_data_home;
} else {
/* It's better to use current lib, to keep paths short */
current_dir[0] = FN_CURLIB;
current_dir[1] = FN_LIBCHAR;
current_dir[2] = 0;
default_path = current_dir;
}
ut_a(default_path);
/* Set InnoDB initialization parameters according to the values
read from MySQL .cnf file */
/*--------------- Data files -------------------------*/
/* The default dir for data files is the datadir of MySQL */
srv_data_home = (innobase_data_home_dir ? innobase_data_home_dir :
default_path);
/* Set default InnoDB data file size to 10 MB and let it be
auto-extending. Thus users can use InnoDB in >= 4.0 without having
to specify any startup options. */
if (!innobase_data_file_path) {
innobase_data_file_path = (char*) "ibdata1:10M:autoextend";
}
/* Since InnoDB edits the argument in the next call, we make another
copy of it: */
internal_innobase_data_file_path = my_strdup(innobase_data_file_path,
MYF(MY_FAE));
ret = (bool) srv_parse_data_file_paths_and_sizes(
internal_innobase_data_file_path);
if (ret == FALSE) {
sql_print_error(
"InnoDB: syntax error in innodb_data_file_path");
mem_free_and_error:
srv_free_paths_and_sizes();
my_free(internal_innobase_data_file_path);
goto error;
}
/* -------------- Log files ---------------------------*/
/* The default dir for log files is the datadir of MySQL */
if (!innobase_log_group_home_dir) {
innobase_log_group_home_dir = default_path;
}
#ifdef UNIV_LOG_ARCHIVE
/* Since innodb_log_arch_dir has no relevance under MySQL,
starting from 4.0.6 we always set it the same as
innodb_log_group_home_dir: */
innobase_log_arch_dir = innobase_log_group_home_dir;
srv_arch_dir = innobase_log_arch_dir;
#endif /* UNIG_LOG_ARCHIVE */
ret = (bool)
srv_parse_log_group_home_dirs(innobase_log_group_home_dir);
if (ret == FALSE || innobase_mirrored_log_groups != 1) {
sql_print_error("syntax error in innodb_log_group_home_dir, or a "
"wrong number of mirrored log groups");
goto mem_free_and_error;
}
/* Validate the file format by animal name */
if (innobase_file_format_name != NULL) {
format_id = innobase_file_format_name_lookup(
innobase_file_format_name);
if (format_id > DICT_TF_FORMAT_MAX) {
sql_print_error("InnoDB: wrong innodb_file_format.");
goto mem_free_and_error;
}
} else {
/* Set it to the default file format id. Though this
should never happen. */
format_id = 0;
}
srv_file_format = format_id;
/* Given the type of innobase_file_format_name we have little
choice but to cast away the constness from the returned name.
innobase_file_format_name is used in the MySQL set variable
interface and so can't be const. */
innobase_file_format_name =
(char*) trx_sys_file_format_id_to_name(format_id);
/* Check innobase_file_format_check variable */
if (!innobase_file_format_check) {
/* Set the value to disable checking. */
srv_max_file_format_at_startup = DICT_TF_FORMAT_MAX + 1;
} else {
/* Set the value to the lowest supported format. */
srv_max_file_format_at_startup = DICT_TF_FORMAT_MIN;
}
/* Did the user specify a format name that we support?
As a side effect it will update the variable
srv_max_file_format_at_startup */
if (innobase_file_format_validate_and_set(
innobase_file_format_max) < 0) {
sql_print_error("InnoDB: invalid "
"innodb_file_format_max value: "
"should be any value up to %s or its "
"equivalent numeric id",
trx_sys_file_format_id_to_name(
DICT_TF_FORMAT_MAX));
goto mem_free_and_error;
}
if (innobase_change_buffering) {
ulint use;
for (use = 0;
use < UT_ARR_SIZE(innobase_change_buffering_values);
use++) {
if (!innobase_strcasecmp(
innobase_change_buffering,
innobase_change_buffering_values[use])) {
ibuf_use = (ibuf_use_t) use;
goto innobase_change_buffering_inited_ok;
}
}
sql_print_error("InnoDB: invalid value "
"innodb_change_buffering=%s",
innobase_change_buffering);
goto mem_free_and_error;
}
innobase_change_buffering_inited_ok:
ut_a((ulint) ibuf_use < UT_ARR_SIZE(innobase_change_buffering_values));
innobase_change_buffering = (char*)
innobase_change_buffering_values[ibuf_use];
/* --------------------------------------------------*/
srv_file_flush_method_str = innobase_file_flush_method;
srv_n_log_groups = (ulint) innobase_mirrored_log_groups;
srv_n_log_files = (ulint) innobase_log_files_in_group;
srv_log_file_size = (ulint) innobase_log_file_size;
#ifdef UNIV_LOG_ARCHIVE
srv_log_archive_on = (ulint) innobase_log_archive;
#endif /* UNIV_LOG_ARCHIVE */
srv_log_buffer_size = (ulint) innobase_log_buffer_size;
srv_buf_pool_size = (ulint) innobase_buffer_pool_size;
srv_buf_pool_instances = (ulint) innobase_buffer_pool_instances;
srv_mem_pool_size = (ulint) innobase_additional_mem_pool_size;
srv_n_file_io_threads = (ulint) innobase_file_io_threads;
srv_n_read_io_threads = (ulint) innobase_read_io_threads;
srv_n_write_io_threads = (ulint) innobase_write_io_threads;
srv_force_recovery = (ulint) innobase_force_recovery;
srv_use_doublewrite_buf = (ibool) innobase_use_doublewrite;
srv_use_checksums = (ibool) innobase_use_checksums;
#ifdef HAVE_LARGE_PAGES
if ((os_use_large_pages = (ibool) my_use_large_pages))
os_large_page_size = (ulint) opt_large_page_size;
#endif
row_rollback_on_timeout = (ibool) innobase_rollback_on_timeout;
srv_locks_unsafe_for_binlog = (ibool) innobase_locks_unsafe_for_binlog;
srv_max_n_open_files = (ulint) innobase_open_files;
srv_innodb_status = (ibool) innobase_create_status_file;
srv_print_verbose_log = mysqld_embedded ? 0 : 1;
/* Store the default charset-collation number of this MySQL
installation */
data_mysql_default_charset_coll = (ulint)default_charset_info->number;
ut_a(DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL ==
my_charset_latin1.number);
ut_a(DATA_MYSQL_BINARY_CHARSET_COLL == my_charset_bin.number);
/* Store the latin1_swedish_ci character ordering table to InnoDB. For
non-latin1_swedish_ci charsets we use the MySQL comparison functions,
and consequently we do not need to know the ordering internally in
InnoDB. */
ut_a(0 == strcmp(my_charset_latin1.name, "latin1_swedish_ci"));
srv_latin1_ordering = my_charset_latin1.sort_order;
innobase_commit_concurrency_init_default();
#ifdef HAVE_PSI_INTERFACE
/* Register keys with MySQL performance schema */
if (PSI_server) {
int count;
count = array_elements(all_pthread_mutexes);
PSI_server->register_mutex("innodb",
all_pthread_mutexes, count);
# ifdef UNIV_PFS_MUTEX
count = array_elements(all_innodb_mutexes);
PSI_server->register_mutex("innodb",
all_innodb_mutexes, count);
# endif /* UNIV_PFS_MUTEX */
# ifdef UNIV_PFS_RWLOCK
count = array_elements(all_innodb_rwlocks);
PSI_server->register_rwlock("innodb",
all_innodb_rwlocks, count);
# endif /* UNIV_PFS_MUTEX */
# ifdef UNIV_PFS_THREAD
count = array_elements(all_innodb_threads);
PSI_server->register_thread("innodb",
all_innodb_threads, count);
# endif /* UNIV_PFS_THREAD */
# ifdef UNIV_PFS_IO
count = array_elements(all_innodb_files);
PSI_server->register_file("innodb",
all_innodb_files, count);
# endif /* UNIV_PFS_IO */
count = array_elements(all_innodb_conds);
PSI_server->register_cond("innodb",
all_innodb_conds, count);
}
#endif /* HAVE_PSI_INTERFACE */
/* Since we in this module access directly the fields of a trx
struct, and due to different headers and flags it might happen that
mutex_t has a different size in this module and in InnoDB
modules, we check at run time that the size is the same in
these compilation modules. */
err = innobase_start_or_create_for_mysql();
if (err != DB_SUCCESS) {
goto mem_free_and_error;
}
innobase_old_blocks_pct = buf_LRU_old_ratio_update(
innobase_old_blocks_pct, TRUE);
innobase_open_tables = hash_create(200);
mysql_mutex_init(innobase_share_mutex_key,
&innobase_share_mutex,
MY_MUTEX_INIT_FAST);
mysql_mutex_init(prepare_commit_mutex_key,
&prepare_commit_mutex, MY_MUTEX_INIT_FAST);
mysql_mutex_init(commit_threads_m_key,
&commit_threads_m, MY_MUTEX_INIT_FAST);
mysql_mutex_init(commit_cond_mutex_key,
&commit_cond_m, MY_MUTEX_INIT_FAST);
mysql_cond_init(commit_cond_key, &commit_cond, NULL);
innodb_inited= 1;
#ifdef MYSQL_DYNAMIC_PLUGIN
if (innobase_hton != p) {
innobase_hton = reinterpret_cast<handlerton*>(p);
*innobase_hton = *innodb_hton_ptr;
}
#endif /* MYSQL_DYNAMIC_PLUGIN */
/* Get the current high water mark format. */
innobase_file_format_max = (char*) trx_sys_file_format_max_get();
DBUG_RETURN(FALSE);
error:
DBUG_RETURN(TRUE);
}
/*******************************************************************//**
Closes an InnoDB database.
@return TRUE if error */
static
int
innobase_end(
/*=========*/
handlerton* hton, /*!< in/out: InnoDB handlerton */
ha_panic_function type __attribute__((unused)))
/*!< in: ha_panic() parameter */
{
int err= 0;
DBUG_ENTER("innobase_end");
DBUG_ASSERT(hton == innodb_hton_ptr);
if (innodb_inited) {
srv_fast_shutdown = (ulint) innobase_fast_shutdown;
innodb_inited = 0;
hash_table_free(innobase_open_tables);
innobase_open_tables = NULL;
if (innobase_shutdown_for_mysql() != DB_SUCCESS) {
err = 1;
}
srv_free_paths_and_sizes();
my_free(internal_innobase_data_file_path);
mysql_mutex_destroy(&innobase_share_mutex);
mysql_mutex_destroy(&prepare_commit_mutex);
mysql_mutex_destroy(&commit_threads_m);
mysql_mutex_destroy(&commit_cond_m);
mysql_cond_destroy(&commit_cond);
}
DBUG_RETURN(err);
}
/****************************************************************//**
Flushes InnoDB logs to disk and makes a checkpoint. Really, a commit flushes
the logs, and the name of this function should be innobase_checkpoint.
@return TRUE if error */
static
bool
innobase_flush_logs(
/*================*/
handlerton* hton) /*!< in/out: InnoDB handlerton */
{
bool result = 0;
DBUG_ENTER("innobase_flush_logs");
DBUG_ASSERT(hton == innodb_hton_ptr);
log_buffer_flush_to_disk();
DBUG_RETURN(result);
}
/****************************************************************//**
Return alter table flags supported in an InnoDB database. */
static
uint
innobase_alter_table_flags(
/*=======================*/
uint flags)
{
return(HA_INPLACE_ADD_INDEX_NO_READ_WRITE
| HA_INPLACE_ADD_INDEX_NO_WRITE
| HA_INPLACE_DROP_INDEX_NO_READ_WRITE
| HA_INPLACE_ADD_UNIQUE_INDEX_NO_READ_WRITE
| HA_INPLACE_ADD_UNIQUE_INDEX_NO_WRITE
| HA_INPLACE_DROP_UNIQUE_INDEX_NO_READ_WRITE
| HA_INPLACE_ADD_PK_INDEX_NO_READ_WRITE);
}
/****************************************************************//**
Unsigned long integer comparison function.
@return an integer less than, equal to, or greater than zero if the
first argument is respectively less than, equal to, or greater than
the second. */
extern "C" UNIV_INTERN
int
ulint_cmp(
/*========*/
const void * a, /*!< in: Array element. */
const void * b) /*!< in: Array element. */
{
ulint v1 = *(const ulint*) a;
ulint v2 = *(const ulint*) b;
if (v1 < v2)
return(-1);
if (v1 > v2)
return(1);
return(0);
}
/****************************************************************//**
Setup parameters and issue a synchronous read request for pages to
be placed into the buffer pool. */
static
longlong
setup_and_prefetch_pages(
/*=====================*/
Item** args, /*!< in: Arguments to command. */
uint args_count) /*!< in: Number of arguments. */
{
ulint ret = -1;
ulint space;
ulint* page_nos;
ulint n_stored;
ulint space_size;
/* Must at least have the tablespace identifier and a page number. */
if (args_count < 2) {
my_error(ER_HTON_CONTROL_INVALID_ARGUMENT, MYF(0));
return(-1);
}
space= args[0]->val_int();
/* Bail out early if the tablespace does not exist. */
if (!fil_tablespace_exists_in_mem(space)) {
my_error(ER_HTON_CONTROL_INVALID_ARGUMENT, MYF(0));
return(-1);
}
/* Prevent the tablespace from being deleted while prefetching pages. */
if (fil_inc_pending_ops(space)) {
my_error(ER_HTON_CONTROL_INVALID_ARGUMENT, MYF(0));
return(-1);
}
/* Get the size of the space in pages. */
space_size = fil_space_get_size(space);
/* Ensure that the file space exists and is a tablespace. */
if (!space_size || (fil_space_get_type(space) != FIL_TABLESPACE)) {
my_error(ER_HTON_CONTROL_INVALID_ARGUMENT, MYF(0));
goto exit;
}
n_stored = args_count - 1;
page_nos = (ulint*) mem_alloc(n_stored * sizeof(ulint));
/* Build an array of page numbers. */
for (ulint i = 0; i < n_stored; i++)
page_nos[i] = (ulint) args[i+1]->val_int();
my_qsort(page_nos, n_stored, sizeof(ulint), ulint_cmp);
/* Ensure that highest page is not outside of the tablespace. */
if (space_size <= page_nos[n_stored - 1]) {
my_error(ER_HTON_CONTROL_INVALID_ARGUMENT, MYF(0));
mem_free(page_nos);
goto exit;
}
/* Read the set of pages into the buffer pool. */
ret = buf_read_pages(TRUE, space, 0, page_nos, n_stored);
mem_free(page_nos);
exit:
fil_decr_pending_ops(space);
return(ret);
}
/****************************************************************//**
Manipulate internal parameters of the storage engine. */
static
longlong
innobase_control(
/*=============*/
handlerton*, /*!< in: Innodb handlerton */
const char* cmd, /*!< in: Control command. */
Item** args, /*!< in: Arguments. */
uint args_count) /*!< in: Number of arguments. */
{
if (0 == innobase_strcasecmp(cmd, "prefetch_pages")) {
return(setup_and_prefetch_pages(args, args_count));
}
my_error(ER_HTON_CONTROL_CMD_NOT_IMPLEMENTED, MYF(0), cmd);
return(0);
}
/*****************************************************************//**
Commits a transaction in an InnoDB database. */
static
void
innobase_commit_low(
/*================*/
trx_t* trx) /*!< in: transaction handle */
{
if (trx_is_started(trx)) {
trx_commit_for_mysql(trx);
}
}
/*****************************************************************//**
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 */
{
trx_t* trx;
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 = check_trx_exists(thd);
/* This is just to play safe: release a possible FIFO ticket and
search latch. Since we will reserve the kernel mutex, we have to
release the search system latch first to obey the latching order. */
innobase_release_stat_resources(trx);
/* If the transaction is not started yet, start it */
trx_start_if_not_started(trx);
/* Assign a read view if the transaction does not have it yet */
trx_assign_read_view(trx);
/* Set the MySQL flag to mark that there is an active transaction */
innobase_register_trx(hton, current_thd, trx);
DBUG_RETURN(0);
}
/*****************************************************************//**
Commits a transaction in an InnoDB database or marks an SQL statement
ended.
@return 0 */
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 all) /*!< in: TRUE - commit transaction
FALSE - the current SQL statement ended */
{
trx_t* trx;
DBUG_ENTER("innobase_commit");
DBUG_ASSERT(hton == innodb_hton_ptr);
DBUG_PRINT("trans", ("ending transaction"));
trx = check_trx_exists(thd);
/* Since we will reserve the kernel mutex, we have to release
the search system latch first to obey the latching order. */
if (trx->has_search_latch) {
trx_search_latch_release_if_reserved(trx);
}
/* 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 MySQL 2PC, "
"but transaction is active");
}
if (all
|| (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
/* We were instructed to commit the whole transaction, or
this is an SQL statement end and autocommit is on */
/* We need current binlog position for ibbackup to work.
Note, the position is current because of
prepare_commit_mutex */
retry:
if (innobase_commit_concurrency > 0) {
mysql_mutex_lock(&commit_cond_m);
commit_threads++;
if (commit_threads > innobase_commit_concurrency) {
commit_threads--;
mysql_cond_wait(&commit_cond,
&commit_cond_m);
mysql_mutex_unlock(&commit_cond_m);
goto retry;
}
else {
mysql_mutex_unlock(&commit_cond_m);
}
}
/* The following calls to read the MySQL binary log
file name and the position return consistent results:
1) Other InnoDB transactions cannot intervene between
these calls as we are holding prepare_commit_mutex.
2) 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.
3) A MySQL log file rotation cannot happen because
MySQL protects against this by having a counter of
transactions in prepared state and it only allows
a rotation when the counter drops to zero. See
LOCK_prep_xids and COND_prep_xids in log.cc. */
trx->mysql_log_file_name = mysql_bin_log_file_name();
trx->mysql_log_offset = (ib_int64_t) mysql_bin_log_file_pos();
if (thd_is_replication_slave_thread(thd)) {
trx->mysql_master_log_file_name =
mysql_master_log_file_name();
trx->mysql_master_log_pos = mysql_master_log_file_pos();
}
/* Don't do write + flush right now. For group commit
to work we want to do the flush after releasing the
prepare_commit_mutex. */
trx->flush_log_later = TRUE;
innobase_commit_low(trx);
trx->flush_log_later = FALSE;
if (innobase_commit_concurrency > 0) {
mysql_mutex_lock(&commit_cond_m);
commit_threads--;
mysql_cond_signal(&commit_cond);
mysql_mutex_unlock(&commit_cond_m);
}
if (trx_has_prepare_commit_mutex(trx)) {
mysql_mutex_unlock(&prepare_commit_mutex);
}
trx_deregister_from_2pc(trx);
/* Now do a write + flush of logs. */
trx_commit_complete_for_mysql(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 */
row_unlock_table_autoinc_for_mysql(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 */
trx_mark_sql_stat_end(trx);
}
trx->n_autoinc_rows = 0; /* Reset the number AUTO-INC rows required */
if (trx->declared_to_be_inside_innodb) {
/* Release our possible ticket in the FIFO */
srv_conc_force_exit_innodb(trx);
}
/* Tell the InnoDB server that there might be work for utility
threads: */
srv_active_wake_master_thread();
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 all) /*!< in: TRUE - commit transaction
FALSE - the current SQL statement ended */
{
int error = 0;
trx_t* trx;
DBUG_ENTER("innobase_rollback");
DBUG_ASSERT(hton == innodb_hton_ptr);
DBUG_PRINT("trans", ("aborting transaction"));
trx = check_trx_exists(thd);
/* Release a possible FIFO ticket and search latch. Since we will
reserve the kernel mutex, we have to release the search system latch
first to obey the latching order. */
innobase_release_stat_resources(trx);
trx->n_autoinc_rows = 0; /* Reset the number AUTO-INC rows required */
/* 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 */
row_unlock_table_autoinc_for_mysql(trx);
if (all
|| !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, NULL));
}
/*****************************************************************//**
Rolls back a transaction
@return 0 or error number */
static
int
innobase_rollback_trx(
/*==================*/
trx_t* trx) /*!< in: transaction */
{
int error = 0;
DBUG_ENTER("innobase_rollback_trx");
DBUG_PRINT("trans", ("aborting transaction"));
/* Release a possible FIFO ticket and search latch. Since we will
reserve the kernel mutex, we have to release the search system latch
first to obey the latching order. */
innobase_release_stat_resources(trx);
/* 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 */
row_unlock_table_autoinc_for_mysql(trx);
error = trx_rollback_for_mysql(trx);
DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL));
}
/*****************************************************************//**
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 */
void* savepoint) /*!< in: savepoint data */
{
ib_int64_t mysql_binlog_cache_pos;
int error = 0;
trx_t* trx;
char name[64];
DBUG_ENTER("innobase_rollback_to_savepoint");
DBUG_ASSERT(hton == innodb_hton_ptr);
trx = check_trx_exists(thd);
/* Release a possible FIFO ticket and search latch. Since we will
reserve the kernel mutex, we have to release the search system latch
first to obey the latching order. */
innobase_release_stat_resources(trx);
/* TODO: use provided savepoint data area to store savepoint data */
longlong2str((ulint)savepoint, name, 36);
error = (int) trx_rollback_to_savepoint_for_mysql(trx, name,
&mysql_binlog_cache_pos);
DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL));
}
/*****************************************************************//**
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 should be rolled back */
void* savepoint) /*!< in: savepoint data */
{
int error = 0;
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((ulint)savepoint, name, 36);
error = (int) trx_release_savepoint_for_mysql(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 */
{
int error = 0;
trx_t* trx;
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.
*/
#ifdef MYSQL_SERVER /* plugins cannot access thd->in_sub_stmt */
DBUG_ASSERT(thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) ||
thd->in_sub_stmt);
#endif /* MYSQL_SERVER */
trx = check_trx_exists(thd);
/* Release a possible FIFO ticket and search latch. Since we will
reserve the kernel mutex, we have to release the search system latch
first to obey the latching order. */
innobase_release_stat_resources(trx);
/* 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((ulint)savepoint,name,36);
error = (int) trx_savepoint_for_mysql(trx, name, (ib_int64_t)0);
DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL));
}
/*****************************************************************//**
Frees a possible InnoDB trx object associated with the current THD.
@return 0 or error number */
static
int
innobase_close_connection(
/*======================*/
handlerton* hton, /*!< in: innobase handlerton */
THD* thd) /*!< in: handle to the MySQL thread of the user
whose resources should be free'd */
{
trx_t* trx;
DBUG_ENTER("innobase_close_connection");
DBUG_ASSERT(hton == innodb_hton_ptr);
trx = thd_to_trx(thd);
ut_a(trx);
if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) {
sql_print_error("Transaction not registered for MySQL 2PC, "
"but transaction is active");
}
if (trx_is_started(trx) && global_system_variables.log_warnings) {
sql_print_warning(
"MySQL is closing a connection that has an active "
"InnoDB transaction. %llu row modifications will "
"roll back.",
(ullint) trx->undo_no);
}
innobase_rollback_trx(trx);
trx_free_for_mysql(trx);
DBUG_RETURN(0);
}
/*****************************************************************//**
Cancel any pending lock request associated with the current THD. */
static
void
innobase_kill_connection(
/*======================*/
handlerton* hton, /*!< in: innobase handlerton */
THD* thd) /*!< in: handle to the MySQL thread being killed */
{
trx_t* trx;
DBUG_ENTER("innobase_kill_connection");
DBUG_ASSERT(hton == innodb_hton_ptr);
mutex_enter(&kernel_mutex);
trx = thd_to_trx(thd);
/* Cancel a pending lock request. */
if (trx && trx->wait_lock) {
lock_cancel_waiting_and_release(trx->wait_lock);
}
mutex_exit(&kernel_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 */
UNIV_INTERN
enum row_type
ha_innobase::get_row_type() const
/*=============================*/
{
if (prebuilt && prebuilt->table) {
const ulint flags = prebuilt->table->flags;
if (UNIV_UNLIKELY(!flags)) {
return(ROW_TYPE_REDUNDANT);
}
ut_ad(flags & DICT_TF_COMPACT);
switch (flags & DICT_TF_FORMAT_MASK) {
case DICT_TF_FORMAT_51 << DICT_TF_FORMAT_SHIFT:
return(ROW_TYPE_COMPACT);
case DICT_TF_FORMAT_ZIP << DICT_TF_FORMAT_SHIFT:
if (flags & DICT_TF_ZSSIZE_MASK) {
return(ROW_TYPE_COMPRESSED);
} else {
return(ROW_TYPE_DYNAMIC);
}
#if DICT_TF_FORMAT_ZIP != DICT_TF_FORMAT_MAX
# error "DICT_TF_FORMAT_ZIP != DICT_TF_FORMAT_MAX"
#endif
}
}
ut_ad(0);
return(ROW_TYPE_NOT_USED);
}
/****************************************************************//**
Get the table flags to use for the statement.
@return table flags */
UNIV_INTERN
handler::Table_flags
ha_innobase::table_flags() const
/*============================*/
{
/* Need to use tx_isolation here since table flags is (also)
called before prebuilt is inited. */
ulong const tx_isolation = thd_tx_isolation(ha_thd());
if (tx_isolation <= ISO_READ_COMMITTED)
return int_table_flags;
return int_table_flags | HA_BINLOG_STMT_CAPABLE;
}
/****************************************************************//**
Gives the file extension of an InnoDB single-table tablespace. */
static const char* ha_innobase_exts[] = {
".ibd",
NullS
};
/****************************************************************//**
Returns the table type (storage engine name).
@return table type */
UNIV_INTERN
const char*
ha_innobase::table_type() const
/*===========================*/
{
return(innobase_hton_name);
}
/****************************************************************//**
Returns the index type. */
UNIV_INTERN
const char*
ha_innobase::index_type(
/*====================*/
uint)
/*!< out: index type */
{
return("BTREE");
}
/****************************************************************//**
Returns the table file name extension.
@return file extension string */
UNIV_INTERN
const char**
ha_innobase::bas_ext() const
/*========================*/
{
return(ha_innobase_exts);
}
/****************************************************************//**
Returns the operations supported for indexes.
@return flags of supported operations */
UNIV_INTERN
ulong
ha_innobase::index_flags(
/*=====================*/
uint,
uint,
bool)
const
{
return(HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER
| HA_READ_RANGE | HA_KEYREAD_ONLY);
}
/****************************************************************//**
Returns the maximum number of keys.
@return MAX_KEY */
UNIV_INTERN
uint
ha_innobase::max_supported_keys() const
/*===================================*/
{
return(MAX_KEY);
}
/****************************************************************//**
Returns the maximum key length.
@return maximum supported key length, in bytes */
UNIV_INTERN
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: max key length is
therefore set to slightly less than 1 / 4 of page size which
is 16 kB; but currently MySQL does not work with keys whose
size is > MAX_KEY_LENGTH */
return(3500);
}
/****************************************************************//**
Returns the key map of keys that are usable for scanning.
@return key_map_full */
UNIV_INTERN
const key_map*
ha_innobase::keys_to_use_for_scanning()
{
return(&key_map_full);
}
/****************************************************************//**
Determines if table caching is supported.
@return HA_CACHE_TBL_ASKTRANSACT */
UNIV_INTERN
uint8
ha_innobase::table_cache_type()
{
return(HA_CACHE_TBL_ASKTRANSACT);
}
/****************************************************************//**
Determines if the primary key is clustered index.
@return true */
UNIV_INTERN
bool
ha_innobase::primary_key_is_clustered()
{
return(true);
}
/** Always normalize table name to lower case on Windows */
#ifdef __WIN__
#define normalize_table_name(norm_name, name) \
normalize_table_name_low(norm_name, name, TRUE)
#else
#define normalize_table_name(norm_name, name) \
normalize_table_name_low(norm_name, name, FALSE)
#endif /* __WIN__ */
/*****************************************************************//**
Normalizes a table name string. A normalized name consists of the
database name catenated to '/' and table name. An 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. */
static
void
normalize_table_name_low(
/*=====================*/
char* norm_name, /*!< out: normalized name as a
null-terminated string */
const char* name, /*!< in: table name string */
ibool set_lower_case) /*!< in: TRUE if we want to set
name to lower case */
{
char* name_ptr;
char* db_ptr;
ulint db_len;
char* ptr;
/* 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;
/* 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;
memcpy(norm_name, db_ptr, db_len);
norm_name[db_len] = '/';
memcpy(norm_name + db_len + 1, name_ptr, strlen(name_ptr) + 1);
if (set_lower_case) {
innobase_casedn_str(norm_name);
}
}
#if !defined(DBUG_OFF)
/*********************************************************************
Test normalize_table_name_low(). */
static
void
test_normalize_table_name_low()
/*===========================*/
{
char norm_name[128];
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_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;
}
}
}
#endif /* !DBUG_OFF */
/********************************************************************//**
Get the upper limit of the MySQL integral and floating-point type.
@return maximum allowed value for the field */
static
ulonglong
innobase_get_int_col_max_value(
/*===========================*/
const Field* field) /*!< in: MySQL 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);
}
/*******************************************************************//**
This function checks whether the index column information
is consistent between KEY info from mysql and that from innodb index.
@return TRUE if all column types match. */
static
ibool
innobase_match_index_columns(
/*=========================*/
const KEY* key_info, /*!< in: Index info
from mysql */
const dict_index_t* index_info) /*!< in: Index info
from Innodb */
{
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->key_parts != index_info->n_user_defined_cols) {
DBUG_RETURN(FALSE);
}
key_part = key_info->key_part;
key_end = key_part + key_info->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) {
ulint col_type;
ibool is_unsigned;
ulint mtype = innodb_idx_fld->col->mtype;
/* Need to translate to InnoDB column type before
comparison. */
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);
}
}
if (col_type != mtype) {
/* Column Type mismatches */
DBUG_RETURN(FALSE);
}
innodb_idx_fld++;
}
DBUG_RETURN(TRUE);
}
/*******************************************************************//**
This function builds a translation table in INNOBASE_SHARE
structure for fast index location with mysql array number from its
table->key_info structure. This also provides the necessary translation
between the key order in mysql key_info and Innodb ib_table->indexes if
they are not fully matched with each other.
Note we do not have any mutex protecting the translation table
building based on the assumption that there is no concurrent
index creation/drop and DMLs that requires index lookup. All table
handle will be closed before the index creation/drop.
@return TRUE if index translation table built successfully */
static
ibool
innobase_build_index_translation(
/*=============================*/
const TABLE* table, /*!< in: table in MySQL data
dictionary */
dict_table_t* ib_table, /*!< in: table in Innodb data
dictionary */
INNOBASE_SHARE* share) /*!< in/out: share structure
where index translation table
will be constructed in. */
{
ulint mysql_num_index;
ulint ib_num_index;
dict_index_t** index_mapping;
ibool ret = TRUE;
DBUG_ENTER("innobase_build_index_translation");
mutex_enter(&dict_sys->mutex);
mysql_num_index = table->s->keys;