/** File format constraint for ALTER TABLE */
ulong innodb_instant_alter_column_allowed;
/** Note we cannot use rec_format_enum because we do not allow
COMPRESSED row format for innodb_default_row_format option. */ enum default_row_format_enum {
DEFAULT_ROW_FORMAT_REDUNDANT = 0,
DEFAULT_ROW_FORMAT_COMPACT = 1,
DEFAULT_ROW_FORMAT_DYNAMIC = 2,
};
/** Checks whether the file name belongs to a to the hlindex @param[in]file_namefilename
@return pointer to the end of the table name part of the file name, or NULL */ static char*
is_hlindex( /*=========*/ char* file_name)
{ /* We look for pattern #i# to see if the table is hlindex
MariaDB table. */ return strstr(file_name, "#i#");
}
/** Return the InnoDB ROW_FORMAT enum value @param[in]row_formatrow_formatfrom"innodb_default_row_format"
@return InnoDB ROW_FORMAT value from rec_format_t enum. */ static
rec_format_t
get_row_format(
ulong row_format)
{ switch(row_format) { case DEFAULT_ROW_FORMAT_REDUNDANT: return(REC_FORMAT_REDUNDANT); case DEFAULT_ROW_FORMAT_COMPACT: return(REC_FORMAT_COMPACT); case DEFAULT_ROW_FORMAT_DYNAMIC: return(REC_FORMAT_DYNAMIC); default:
ut_ad(0); return(REC_FORMAT_DYNAMIC);
}
}
/** Possible values for system variable "innodb_stats_method". The values aredefinedthesameasitscorrespondingMyISAMsystemvariable
"myisam_stats_method"(see "myisam_stats_method_names"), for better usability */ staticconstchar* 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 =
CREATE_TYPELIB_FOR(innodb_stats_method_names);
/** Possible values for system variable "innodb_linux_aio" */ #ifdef __linux__ constchar* innodb_linux_aio_names[] = { "auto", /* SRV_LINUX_AIO_AUTO */ "io_uring", /* SRV_LINUX_AIO_IO_URING */ "aio", /* SRV_LINUX_AIO_LIBAIO */
NullS
};
/** Used to define an enumerate type of the system variable
innodb_linux_aio. Used by mariadb-backup too. */
TYPELIB innodb_linux_aio_typelib = CREATE_TYPELIB_FOR(innodb_linux_aio_names); #endif
/** Possible values of the parameter innodb_checksum_algorithm */ constchar* innodb_checksum_algorithm_names[] = { "crc32", "strict_crc32", "full_crc32", "strict_full_crc32",
NullS
};
/** Used to define an enumerate type of the system variable
innodb_checksum_algorithm. */
TYPELIB innodb_checksum_algorithm_typelib =
CREATE_TYPELIB_FOR(innodb_checksum_algorithm_names);
/** Possible values for system variable "innodb_default_row_format". */ staticconstchar* innodb_default_row_format_names[] = { "redundant", "compact", "dynamic",
NullS
};
/** Used to define an enumerate type of the system variable
innodb_default_row_format. */ static TYPELIB innodb_default_row_format_typelib =
CREATE_TYPELIB_FOR(innodb_default_row_format_names);
/** Names of allowed values of innodb_flush_method */ staticconstchar* innodb_flush_method_names[] = { "fsync", "O_DSYNC", "littlesync", "nosync", "O_DIRECT", "O_DIRECT_NO_FSYNC", #ifdef _WIN32 "unbuffered", "async_unbuffered"/* alias for "unbuffered" */, "normal"/* alias for "fsync" */, #endif
NullS
};
/** Names of allowed values of innodb_doublewrite */ staticconstchar *innodb_doublewrite_names[]=
{"OFF", "ON", "fast", nullptr};
/** Enumeration of innodb_doublewrite */
TYPELIB innodb_doublewrite_typelib=
CREATE_TYPELIB_FOR(innodb_doublewrite_names);
/** Names of allowed values of innodb_deadlock_report */ staticconstchar *innodb_deadlock_report_names[]= { "off", /* Do not report any details of deadlocks */ "basic", /* Report waiting transactions and lock requests */ "full", /* Also report blocking locks */
NullS
};
/** Enumeration of innodb_deadlock_report */ static TYPELIB innodb_deadlock_report_typelib =
CREATE_TYPELIB_FOR(innodb_deadlock_report_names);
/** Allowed values of innodb_instant_alter_column_allowed */ constchar* innodb_instant_alter_column_allowed_names[] = { "never", /* compatible with MariaDB 5.5 to 10.2 */ "add_last",/* allow instant ADD COLUMN ... LAST */ "add_drop_reorder", /* allow instant ADD anywhere & DROP & reorder */
NullS
};
/** Enumeration of innodb_instant_alter_column_allowed */ static TYPELIB innodb_instant_alter_column_allowed_typelib =
CREATE_TYPELIB_FOR(innodb_instant_alter_column_allowed_names);
/** Retrieve the FTS Relevance Ranking result for doc with doc_id ofm_prebuilt->fts_doc_id @param[in,out]fts_hdlFTShandler
@return the relevance ranking value */ static float
innobase_fts_retrieve_ranking(
FT_INFO* fts_hdl); /** Free the memory for the FTS handler
@param[in,out] fts_hdl FTS handler */ static void
innobase_fts_close_ranking(
FT_INFO* fts_hdl); /** Find and Retrieve the FTS Relevance Ranking result for doc with doc_id ofm_prebuilt->fts_doc_id @param[in,out]fts_hdlFTShandler
@return the relevance ranking value */ static float
innobase_fts_find_ranking(
FT_INFO* fts_hdl,
uchar*,
uint);
/* Call back function array defined by MySQL and used to
retrieve FTS results. */ conststruct _ft_vft ft_vft_result = {NULL,
innobase_fts_find_ranking,
innobase_fts_close_ranking,
innobase_fts_retrieve_ranking,
NULL};
/** @return version of the extended FTS API */ static
uint
innobase_fts_get_version()
{ /* Currently this doesn't make much sense as returning HA_CAN_FULLTEXT_EXTautomaticallymeanthisversionissupported.
This supposed to ease future extensions. */ return(2);
}
/** @return Which part of the extended FTS API is supported */ static
ulonglong
innobase_fts_flags()
{ return(FTS_ORDERED_RESULT | FTS_DOCID_IN_RESULT);
}
/** Find and Retrieve the FTS doc_id for the current result row @param[in,out]fts_hdlFTShandler
@return the document ID */ static
ulonglong
innobase_fts_retrieve_docid(
FT_INFO_EXT* fts_hdl);
/** Find and retrieve the size of the current result @param[in,out]fts_hdlFTShandler
@return number of matching rows */ static
ulonglong
innobase_fts_count_matches(
FT_INFO_EXT* fts_hdl) /*!< in: FTS handler */
{
NEW_FT_INFO* handle = reinterpret_cast<NEW_FT_INFO*>(fts_hdl);
#ifdef HAVE_PSI_INTERFACE # define PSI_KEY(n) {&n##_key, #n, 0} /* Keys to register pthread mutexes in the current file with
performance schema */ static mysql_pfs_key_t pending_checkpoint_mutex_key;
# ifdef UNIV_PFS_THREAD /* all_innodb_threads array contains threads that are performanceschemainstrumentedif"UNIV_PFS_THREAD"
is defined */ static PSI_thread_info all_innodb_threads[] = {
{&page_cleaner_thread_key, "page_cleaner", 0},
{&trx_rollback_clean_thread_key, "trx_rollback", 0},
{&page_encrypt_thread_key, "page_encrypt", 0},
{&thread_pool_thread_key,"ib_tpool_worker", 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[] = {
PSI_KEY(innodb_data_file),
PSI_KEY(innodb_temp_file)
}; # endif /* UNIV_PFS_IO */ #endif/* HAVE_PSI_INTERFACE */
// should page compression be used by default for new tables static MYSQL_THDVAR_BOOL(compression_default, PLUGIN_VAR_OPCMDARG, "Is compression the default for new tables",
NULL, NULL, FALSE);
/** Update callback for SET [SESSION] innodb_default_encryption_key_id */ staticvoid
innodb_default_encryption_key_id_update(THD* thd, st_mysql_sys_var* var, void* var_ptr, constvoid *save)
{
uint key_id = *static_cast<const uint*>(save); if (key_id != FIL_DEFAULT_ENCRYPTION_KEY
&& !encryption_key_id_exists(key_id)) {
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_WRONG_ARGUMENTS, "innodb_default_encryption_key=%u" " is not available", key_id);
}
*static_cast<uint*>(var_ptr) = key_id;
}
static MYSQL_THDVAR_UINT(default_encryption_key_id, PLUGIN_VAR_RQCMDARG, "Default encryption key id used for table encryption",
NULL, innodb_default_encryption_key_id_update,
FIL_DEFAULT_ENCRYPTION_KEY, 1, UINT_MAX32, 0);
ha_create_table_option innodb_table_option_list[]=
{ /* With this option user can enable page compression feature for the
table */
HA_TOPTION_SYSVAR("PAGE_COMPRESSED", page_compressed, compression_default), /* With this option user can set zip compression level for page
compression for this table*/
HA_TOPTION_NUMBER("PAGE_COMPRESSION_LEVEL", page_compression_level, 0, 1, 9, 1), /* With this option the user can enable encryption for the table */
HA_TOPTION_ENUM("ENCRYPTED", encryption, "DEFAULT,YES,NO", 0), /* With this option the user defines the key identifier using for the encryption */
HA_TOPTION_SYSVAR("ENCRYPTION_KEY_ID", encryption_key_id, default_encryption_key_id),
HA_TOPTION_ENUM("ADAPTIVE_HASH_INDEX", adaptive_hash_index,
table_hint_options, TABLE_HINT_DEFAULT),
HA_TOPTION_END
};
constexpr int max_bytes_from_incomplete_field= REDUNDANT_REC_MAX_DATA_SIZE;
constexpr int max_complete_fields= 64; /* 32 PK + 32 secondary index fields */
static_assert(
max_complete_fields <= dict_index_t::ahi::max_fields, "max_complete_fields too large for dict_index_t::ahi"
);
static_assert(
max_bytes_from_incomplete_field <= dict_index_t::ahi::max_bytes, "max_bytes_from_incomplete_field too large for dict_index_t::ahi"
); /* Max value in table_hint_options is TABLE_HINT_NO */
static_assert(TABLE_HINT_NO <= dict_index_t::ahi::max_enabled, "TABLE_HINT enum values too large for dict_index_t::ahi"
);
/*************************************************************//**
Check whether valid argument given to innodb_ft_*_stopword_table. This function is registered as a callback with MySQL.
@return0for valid stopword table */ static int
innodb_stopword_table_validate( /*===========================*/
THD* thd, /*!< in: thread handle */ struct st_mysql_sys_var* var, /*!< in: pointer to system
variable */ void* save, /*!< out: immediate result
for update function */ struct st_mysql_value* value); /*!< in: incoming string */
push_warning_printf(
thd, Sql_condition::WARN_LEVEL_WARN,
ER_WRONG_ARGUMENTS, "InnoDB: Path doesn't exist.");
*static_cast<constchar**>(save) = NULL; return(1);
} elseif (my_access(tmp_abs_path, R_OK | W_OK)) {
push_warning_printf(
thd, Sql_condition::WARN_LEVEL_WARN,
ER_WRONG_ARGUMENTS, "InnoDB: Server doesn't have permission in " "the given location.");
*static_cast<constchar**>(save) = NULL; return(1);
}
MY_STAT stat_info_dir;
if (my_stat(tmp_abs_path, &stat_info_dir, MYF(0))) { if ((stat_info_dir.st_mode & S_IFDIR) != S_IFDIR) {
push_warning_printf(
thd, Sql_condition::WARN_LEVEL_WARN,
ER_WRONG_ARGUMENTS, "Given path is not a directory. ");
*static_cast<constchar**>(save) = NULL; return(1);
}
}
if (!is_mysql_datadir_path(tmp_abs_path)) {
push_warning_printf(
thd, Sql_condition::WARN_LEVEL_WARN,
ER_WRONG_ARGUMENTS, "InnoDB: Path Location should not be same as " "mysql data directory location.");
*static_cast<constchar**>(save) = NULL; return(1);
}
/** Gets field offset for a field in a table. @param[in]tableMySQLtableobject @param[in]fieldMySQLfieldobject(fromtable->fieldarray)
@return offset */ staticinline
uint
get_field_offset( const TABLE* table, const Field* field)
{ return field->offset(table->record[0]);
}
/*************************************************************//**
Check for a valid value of innobase_compression_algorithm.
@return0for valid innodb_compression_algorithm. */ static int
innodb_compression_algorithm_validate( /*==================================*/
THD* thd, /*!< in: thread handle */ struct st_mysql_sys_var* var, /*!< in: pointer to system
variable */ void* save, /*!< out: immediate result
for update function */ struct st_mysql_value* value); /*!< in: incoming string */
static MYSQL_THDVAR_BOOL(ft_enable_stopword, PLUGIN_VAR_OPCMDARG, "Create FTS index with stopword",
NULL, NULL, /* default */ TRUE);
static MYSQL_THDVAR_BOOL(table_lock_on_full_scan, PLUGIN_VAR_OPCMDARG, "When the SQL layer advises a full scan, acquire a single table lock " "instead of locking each scanned row individually. This is faster, at the " "cost of less granular (table-level instead of record-level) locking",
NULL, NULL, FALSE);
static MYSQL_THDVAR_UINT(lock_wait_timeout, PLUGIN_VAR_RQCMDARG, "Timeout in seconds an InnoDB transaction may wait for a lock before being rolled back. The value 100000000 is infinite timeout",
NULL, NULL, 50, 0, 100000000, 0);
static MYSQL_THDVAR_STR(ft_user_stopword_table,
PLUGIN_VAR_OPCMDARG|PLUGIN_VAR_MEMALLOC, "User supplied stopword table name, effective in the session level",
innodb_stopword_table_validate, NULL, NULL);
/** Cancel any pending lock request associated with the current THD.
@sa THD::awake() @sa ha_kill_query() */ staticvoid innobase_kill_query(handlerton*, THD* thd, enum thd_kill_levels); staticvoid innobase_commit_ordered(THD* thd, bool all);
/*****************************************************************//**
Commits a transaction in an InnoDB database or marks an SQL statement
ended.
@return0 */ static int
innobase_commit( /*============*/
THD* thd, /*!< in: MySQL thread handle of the userforwhomthetransactionshould
be committed */ bool commit_trx); /*!< in: true - commit transaction false-thecurrentSQLstatement
ended */
/*****************************************************************//**
Rolls back a transaction to a savepoint.
@return0if success, HA_ERR_NO_SAVEPOINT if no savepoint with the
given name */ static int
innobase_rollback( /*==============*/
THD* thd, /*!< in: handle to the MySQL thread oftheuserwhosetransactionshould
be rolled back */ bool rollback_trx); /*!< in: TRUE - rollback entire transactionFALSE-rollbackthecurrent
statement only */
/*****************************************************************//**
Rolls back a transaction to a savepoint.
@return0if success, HA_ERR_NO_SAVEPOINT if no savepoint with the
given name */ static int
innobase_rollback_to_savepoint( /*===========================*/
THD* thd, /*!< in: handle to the MySQL thread of theuserwhoseXAtransactionshould
be rolled back to savepoint */ void* savepoint); /*!< in: savepoint data */
/*****************************************************************//**
Check whether innodb state allows to safely release MDL locks after
rollback to savepoint.
@returntrueif it is safe, falseif its not safe. */ static bool
innobase_rollback_to_savepoint_can_release_mdl( /*===========================================*/
THD* thd); /*!< in: handle to the MySQL thread of theuserwhoseXAtransactionshould
be rolled back to savepoint */
/** Requests for log flushes */ struct log_flush_request
{ /** earlier request (for a smaller LSN) */
log_flush_request *next; /** parameter provided to innodb_log_flush_request() */ void *cookie; /** log sequence number that is being waited for */
lsn_t lsn;
};
/** Buffer of pending innodb_log_flush_request() */
alignas(CPU_LEVEL1_DCACHE_LINESIZE) static struct
{ /** first request */
std::atomic<log_flush_request*> start; /** last request */
log_flush_request *end; /** mutex protecting this object */
mysql_mutex_t mutex;
}
log_requests;
/** Adjust some InnoDB startup parameters based on the data directory */ staticvoid innodb_params_adjust();
/*******************************************************************//** This function is used to prepare an X/Open XA distributed transaction.
@return0or error number */ static int
innobase_xa_prepare( /*================*/
THD* thd, /*!< in: handle to the MySQL thread of theuserwhoseXAtransactionshould
be prepared */ bool all); /*!< in: true - prepare transaction false-thecurrentSQLstatement
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( /*================*/
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
@return0or error number */ static int
innobase_commit_by_xid( /*===================*/
XID* xid); /*!< in: X/Open XA transaction
identification */ #ifndef EMBEDDED_LIBRARY /** Inbinlogrecovery,persistentlymarkthatatransactionwillbe rolledback.
/** Ignore FOREIGN KEY constraints that would be violated by DROP DATABASE */ static ibool innodb_drop_database_ignore_fk(void*,void*) { returnfalse; }
/** FOREIGN KEY error reporting context for DROP DATABASE */ struct innodb_drop_database_fk_report
{ /** database name, with trailing '/' */ const span<constchar> name; /** whether errors were found */ bool violated;
};
/** Report FOREIGN KEY constraints that would be violated by DROP DATABASE
@return whether processing should continue */ static ibool innodb_drop_database_fk(void *node, void *report)
{ auto s= static_cast<sel_node_t*>(node); auto r= static_cast<innodb_drop_database_fk_report*>(report); const dfield_t *name= que_node_get_val(s->select_list);
ut_ad(name->type.mtype == DATA_VARCHAR);
if (name->len == UNIV_SQL_NULL || name->len <= r->name.size() ||
memcmp(static_cast<constchar*>(name->data), r->name.data(),
r->name.size())) returnfalse; /* End of matches */
if (id->len != UNIV_SQL_NULL)
sql_print_error("DROP DATABASE: table %.*s is referenced" " by FOREIGN KEY %.*s", static_cast<int>(name->len), static_cast<constchar*>(name->data), static_cast<int>(id->len), static_cast<constchar*>(id->data)); else
ut_ad("corrupted SYS_FOREIGN record" == 0);
returntrue;
}
/** After DROP DATABASE executed ha_innobase::delete_table() on all tablesthatitwasawareof,dropanyleftovertablesinsideInnoDB.
@param path database path */ staticvoid innodb_drop_database(handlerton*, char *path)
{ if (high_level_read_only || recv_sys.rpo) return;
"DECLARE CURSOR fkf IS\n" "SELECT ID FROM SYS_FOREIGN WHERE ID >= :db FOR UPDATE;\n"
"DECLARE CURSOR fkr IS\n" "SELECT REF_NAME,ID FROM SYS_FOREIGN WHERE REF_NAME >= :db FOR UPDATE\n" "ORDER BY REF_NAME;\n"
"DECLARE CURSOR tab IS\n" "SELECT ID,NAME FROM SYS_TABLES WHERE NAME >= :db FOR UPDATE;\n"
"DECLARE CURSOR idx IS\n" "SELECT ID FROM SYS_INDEXES WHERE TABLE_ID = tid FOR UPDATE;\n"
"BEGIN\n"
"OPEN fkf;\n" "WHILE 1 = 1 LOOP\n" " FETCH fkf INTO fk;\n" " IF (SQL % NOTFOUND) THEN EXIT; END IF;\n" " IF TO_BINARY(SUBSTR(fk, 0, LENGTH(:db)))<>TO_BINARY(:db)" " THEN EXIT; END IF;\n" " DELETE FROM SYS_FOREIGN_COLS WHERE TO_BINARY(ID)=TO_BINARY(fk);\n" " DELETE FROM SYS_FOREIGN WHERE CURRENT OF fkf;\n" "END LOOP;\n" "CLOSE fkf;\n"
"OPEN fkr;\n" "FETCH fkr INTO fk_report();\n" "CLOSE fkr;\n"
"OPEN tab;\n" "WHILE 1 = 1 LOOP\n" " FETCH tab INTO tid,name;\n" " IF (SQL % NOTFOUND) THEN EXIT; END IF;\n" " IF TO_BINARY(SUBSTR(name, 0, LENGTH(:db))) <> TO_BINARY(:db)" " THEN EXIT; END IF;\n" " DELETE FROM SYS_COLUMNS WHERE TABLE_ID=tid;\n" " DELETE FROM SYS_TABLES WHERE ID=tid;\n" " OPEN idx;\n" " WHILE 1 = 1 LOOP\n" " FETCH idx INTO iid;\n" " IF (SQL % NOTFOUND) THEN EXIT; END IF;\n" " DELETE FROM SYS_FIELDS WHERE INDEX_ID=iid;\n" " DELETE FROM SYS_INDEXES WHERE CURRENT OF idx;\n" " END LOOP;\n" " CLOSE idx;\n" "END LOOP;\n" "CLOSE tab;\n"
"END;\n";
innodb_drop_database_fk_report report{{namebuf, len + 1}, false};
if (err != DB_SUCCESS)
{
trx->rollback();
sql_print_error("InnoDB: DROP DATABASE %.*s: %s", int(len), namebuf, ut_strerr(err));
} else
trx->commit();
row_mysql_unlock_data_dictionary(trx); if (!stats_failed)
stats.close();
if (err == DB_SUCCESS)
{ /* Eventually after the DELETE FROM SYS_INDEXES was committed, purgewouldinvokedict_drop_index_tree()todeletetheassociated tablespaces.BecausetheSQLlayerexpectsthedirectorytobeempty, wewill"manually"purgethetablespacesthatbelongtothe
records that we delete-marked. */
for (; btr_pcur_is_on_user_rec(&pcur);
btr_pcur_move_to_next_user_rec(&pcur, &mtr))
{ const rec_t *rec= btr_pcur_get_rec(&pcur); if (rec_get_n_fields_old(rec) != DICT_NUM_FIELDS__SYS_TABLES)
{
ut_ad("corrupted SYS_TABLES record" == 0); break;
} if (!rec_get_deleted_flag(rec, false)) continue;
ulint flen;
static_assert(DICT_FLD__SYS_TABLES__NAME == 0, "compatibility");
rec_get_nth_field_offs_old(rec, 0, &flen); if (flen == UNIV_SQL_NULL || flen <= len || memcmp(rec, namebuf, len)) /* We ran out of tables that had existed in the database. */ break; const byte *db_trx_id=
rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__DB_TRX_ID, &flen); if (flen != 6)
{
ut_ad("corrupted SYS_TABLES.SPACE" == 0); break;
} if (mach_read_from_6(db_trx_id) != trx_id) /* This entry was modified by some other transaction than us. Unfortunately,becauseSYS_TABLES.NAMEisthePRIMARYKEY, wecannotdistinguishRENAMEandDROPhere.Itispossible
that the table had been renamed to some other database. */ continue; const byte *s=
rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__SPACE, &flen); if (flen != 4)
ut_ad("corrupted SYS_TABLES.SPACE" == 0); elseif (uint32_t space_id= mach_read_from_4(s))
{
pfs_os_file_t detached= fil_delete_tablespace(space_id); if (detached != OS_FILE_CLOSED)
to_close.emplace_back(detached);
}
}
err_exit:
mtr.commit(); for (pfs_os_file_t detached : to_close)
os_file_close(detached);
/* Any changes must be persisted before we return. */ if (mtr.commit_lsn())
log_write_up_to(mtr.commit_lsn(), true);
}
trx->clear_and_free();
my_free(namebuf);
}
/** Shut down the InnoDB storage engine.
@return 0 */ static int
innobase_end(handlerton*, ha_panic_function);
/*****************************************************************//**
Creates an InnoDB transaction structfor 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.
@return0 */ static int
innobase_start_trx_and_assign_read_view( /*====================================*/
THD* thd); /* in: MySQL thread handle of the userforwhomthetransactionshould
be committed */
/** Flush InnoDB redo logs to the file system.
@return false */ staticbool innobase_flush_logs(handlerton*)
{
ut_ad(!srv_read_only_mode || recv_sys.rpo);
if (!recv_sys.rpo) /* Write and flush any outstanding redo log. */
log_buffer_flush_to_disk(true); returnfalse;
}
/************************************************************************//**
Implements the SHOW ENGINE INNODB STATUS command. Sends the output of the
InnoDB Monitor to the client.
@return0 on success */ static int
innodb_show_status( /*===============*/
handlerton* hton, /*!< in: the innodb handlerton */
THD* thd, /*!< in: the MySQL query thread of
the caller */
stat_print_fn* stat_print); /************************************************************************//** Return0 on success and non-zero on failure. Note: the boolreturn type
seems to be abused here, should be an int. */ static bool
innobase_show_status( /*=================*/
handlerton* hton, /*!< in: the innodb handlerton */
THD* thd, /*!< in: the MySQL query thread of
the caller */
stat_print_fn* stat_print, enum ha_stat_type stat_type);
/** After ALTER TABLE, recompute statistics. */ inlinevoid ha_innobase::reload_statistics()
{ if (dict_table_t *table= m_prebuilt ? m_prebuilt->table : nullptr)
{ if (table->is_readable())
statistics_init(table, true); else
table->stat.fetch_or(dict_table_t::STATS_INITIALIZED);
}
}
/** After ALTER TABLE, recompute statistics. */ staticint innodb_notify_tabledef_changed(handlerton *,
LEX_CSTRING *, LEX_CSTRING *,
LEX_CUSTRING *, LEX_CUSTRING *,
handler *handler)
{
DBUG_ENTER("innodb_notify_tabledef_changed"); if (handler) static_cast<ha_innobase*>(handler)->reload_statistics();
DBUG_RETURN(0);
}
/****************************************************************//**
Parse and enable InnoDB monitor counters during server startup.
User can enable monitor counters/groups by specifying "loose-innodb_monitor_enable = monitor_name1;monitor_name2..."
in server configuration file or at the command line. */ static void
innodb_enable_monitor_at_startup( /*=============================*/ char* str); /*!< in: monitor counter enable list */
/** Check that a page_size is correct for InnoDB. Ifcorrect,settheassociatedpage_size_shiftwhichisthepowerof2 forthispagesize. @param[in]page_sizePageSizetoevaluate
@return an associated page_size_shift if valid, 0 if invalid. */ inline uint32_t innodb_page_size_validate(ulong page_size)
{
DBUG_ENTER("innodb_page_size_validate");
for (uint32_t n = UNIV_PAGE_SIZE_SHIFT_MIN;
n <= UNIV_PAGE_SIZE_SHIFT_MAX;
n++) { if (page_size == static_cast<ulong>(1 << n)) {
DBUG_RETURN(n);
}
}
DBUG_RETURN(0);
}
static MYSQL_THDVAR_BOOL(background_thread,
PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_NOSYSVAR, "Internal (not user visible) flag to mark " "background purge threads", NULL, NULL, 0);
/** Create a MYSQL_THD for a background thread and mark it as such. @paramnamethreadinfoforSHOWPROCESSLIST
@return new MYSQL_THD */
MYSQL_THD innobase_create_background_thd(constchar* name)
{
MYSQL_THD thd= create_background_thd();
thd_proc_info(thd, name);
THDVAR(thd, background_thread) = true; return thd;
}
/******************************************************************//**
Returns the NUL terminated value of glob_hostname.
@return pointer to glob_hostname. */ constchar*
server_get_hostname() /*=================*/
{ return(glob_hostname);
}
/******************************************************************//**
Returns the lock wait timeout for the current connection.
@return the lock wait timeout, in seconds */
uint&
thd_lock_wait_timeout( /*==================*/
THD* thd) /*!< in: thread handle, or NULL to query
the global innodb_lock_wait_timeout */
{ /* According to <mysql/plugin.h>, passing thd == NULL
returns the global value of the session variable. */ return(THDVAR(thd, lock_wait_timeout));
}
/** Get the value of innodb_tmpdir. @param[in]thdthreadhandle,orNULLtoquery theglobalinnodb_tmpdir.
@retval NULL if innodb_tmpdir="" */ constchar *thd_innodb_tmpdir(THD *thd)
{ constchar* tmp_dir = THDVAR(thd, tmpdir);
#ifdef WITH_WSREP /********************************************************************//**
Obtain the InnoDB transaction id of a MySQL thread.
@return transaction id */
__attribute__((warn_unused_result, nonnull))
ulonglong
thd_to_trx_id(
THD* thd) /*!< in: MySQL thread */
{ return(thd_to_trx(thd)->id);
}
Letusperformalogcheckpointtoensurethattheentirebuffer poolisclean,sothatnowritestopersistentfileswillbe possibleduringthesnapshot,andtoguaranteethatnocrash
recovery will be necessary when starting up on the snapshot. */
log_make_checkpoint();
wsrep_sst_disable_writes= true; /* If any FILE_MODIFY records were written by the checkpoint, an extrawriteofaFILE_CHECKPOINTrecordcouldstillbeinvokedby buf_flush_page_cleaner().Letusensurethatthepagecleaner isidleandwillobserveouraboveassignment(notwriteanything
further to the log). */
log_make_checkpoint();
ut_d(recv_no_log_write= true); /* If this were not a no-op, an assertion would fail due to
recv_no_log_write. */
ut_d(log_make_checkpoint());
}
staticvoid sst_enable_innodb_writes()
{
ut_ad(recv_no_log_write);
ut_d(recv_no_log_write= false);
dict_stats_start();
purge_sys.resume();
wsrep_sst_disable_writes= false; /* Allow fts_optimize_callback() to assert that the flag is clear. */
fts_optimize_resume(); const uint old_count= srv_n_fil_crypt_threads;
srv_n_fil_crypt_threads= 0;
fil_crypt_set_thread_cnt(old_count);
}
staticvoid innodb_disable_internal_writes(bool disable)
{ /* thisworksonlyintheSSTdonorthreadandisnotyetfixed toworkinanormalconnectionthread
*/ if (thd_get_thread_id(current_thd)) // if normal thread return; if (disable)
sst_disable_innodb_writes(); else
sst_enable_innodb_writes();
}
ulonglong ha_innobase::table_version() const
{ /* This is either "garbage" or something that was assigned
on a successful ha_innobase::prepare_inplace_alter_table(). */ return m_prebuilt->trx_id;
}
#ifdef UNIV_DEBUG /** whether the DDL log recovery has been completed */ staticbool ddl_recovery_done; #endif
/** Drop any garbage intermediate tables that existed in the system afterabackupwasrestored.
InafinalphaseofMariabackup,thecommitofDDLoperationsisblocked, andthoseDDLoperationswillhavetoberolledback.Becausethe normalDDLrecoverywillnotrunduetothelackofthelogfile, atleastsome#sql-alter-garbagetablesmayremainintheInnoDB datadictionary(whilethedatafilesthemselvesaremissing).
We will attempt to drop the tables here. */ staticvoid drop_garbage_tables_after_restore()
{
btr_pcur_t pcur;
trx_t *trx= trx_create();
mtr_t mtr{trx};
if (!recv_sys.rpo && srv_operation <= SRV_OPERATION_EXPORT_RESTORED &&
srv_force_recovery < SRV_FORCE_NO_BACKGROUND)
{ if (srv_start_after_restore)
drop_garbage_tables_after_restore();
srv_init_purge_tasks();
} return0;
}
/** Report an aborted transaction or statement. @paramthdexecutioncontext @paramalltrue=transaction,false=statement
@param err InnoDB error code */ staticvoid innodb_transaction_abort(THD *thd, bool all, dberr_t err) noexcept
{ if (!thd) return; if (!all); elseif (trx_t *trx = thd_to_trx(thd))
{
ut_ad(trx->state == TRX_STATE_NOT_STARTED);
trx->state= TRX_STATE_ABORTED; if (thd_log_warnings(thd) >= 4)
sql_print_error("InnoDB: Transaction was aborted due to %s",
ut_strerr(err));
}
thd->mark_transaction_to_rollback(all);
}
/********************************************************************//**
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 */ staticint
convert_error_code_to_mysql( /*========================*/
dberr_t error, /*!< in: InnoDB error code */
ulint flags, /*!< in: InnoDB table flags, or 0 */
THD* thd) /*!< in: user thread handle or NULL */
{ switch (error) { case DB_SUCCESS: return(0);
case DB_INTERRUPTED: return(HA_ERR_ABORTED_BY_USER);
case DB_FOREIGN_EXCEED_MAX_CASCADE:
ut_ad(thd);
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
HA_ERR_ROW_IS_REFERENCED, "InnoDB: Cannot delete/update " "rows with cascading foreign key " "constraints that exceed max " "depth of %d. Please " "drop extra constraints and try " "again", FK_MAX_CASCADE_DEL); return(HA_ERR_FK_DEPTH_EXCEEDED);
case DB_CANT_CREATE_GEOMETRY_OBJECT:
my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0)); return(HA_ERR_NULL_IN_SPATIAL);
case DB_ERROR: default: return(HA_ERR_GENERIC); /* unspecified error */
case DB_DUPLICATE_KEY: /* Be cautious with returning this error, since mysqlcouldre-enterthestoragelayertoget duplicatedkeyinfo,theoperationrequiresa validtablehandleand/ortransactioninformation, whichmightnotalwaysbeavailableintheerror
handling stage. */ return(HA_ERR_FOUND_DUPP_KEY);
case DB_READ_ONLY: return(HA_ERR_TABLE_READONLY);
case DB_FOREIGN_DUPLICATE_KEY: return(HA_ERR_FOREIGN_DUPLICATE_KEY);
case DB_MISSING_HISTORY: return(HA_ERR_TABLE_DEF_CHANGED);
case DB_RECORD_NOT_FOUND: return(HA_ERR_NO_ACTIVE_RECORD);
case DB_DEADLOCK: case DB_RECORD_CHANGED: /* Since we rolled back the whole transaction, the
cached binlog must be emptied. */
innodb_transaction_abort(thd, true, error); return error == DB_DEADLOCK
? HA_ERR_LOCK_DEADLOCK : HA_ERR_RECORD_CHANGED;
case DB_LOCK_WAIT_TIMEOUT: /* Starting from 5.0.13, we let MySQL just roll back the latestSQLstatementinalockwaittimeout.Previously,we
rolled back the whole transaction. */
case DB_NO_REFERENCED_ROW: return(HA_ERR_NO_REFERENCED_ROW);
case DB_ROW_IS_REFERENCED: return(HA_ERR_ROW_IS_REFERENCED);
case DB_NO_FK_ON_S_BASE_COL: case DB_CANNOT_ADD_CONSTRAINT: case DB_CHILD_NO_INDEX: case DB_PARENT_NO_INDEX: return(HA_ERR_CANNOT_ADD_FOREIGN);
case DB_CANNOT_DROP_CONSTRAINT:
return(HA_ERR_ROW_IS_REFERENCED); /* TODO: This is a bit misleading,anewMySQLerror
code should be introduced */
case DB_CORRUPTION: case DB_PAGE_CORRUPTED: return(HA_ERR_CRASHED);
case DB_OUT_OF_FILE_SPACE: return(HA_ERR_RECORD_FILE_FULL);
case DB_TEMP_FILE_WRITE_FAIL: /* This error can happen during
copy_data_between_tables() or bulk insert operation */
innodb_transaction_abort(thd,
innobase_rollback_on_timeout,
error);
my_error(ER_GET_ERRMSG, MYF(0),
DB_TEMP_FILE_WRITE_FAIL,
ut_strerr(DB_TEMP_FILE_WRITE_FAIL), "InnoDB"); return(HA_ERR_INTERNAL_ERROR);
case DB_TABLE_NOT_FOUND: return(HA_ERR_NO_SUCH_TABLE);
case DB_DECRYPTION_FAILED: return(HA_ERR_DECRYPTION_FAILED);
case DB_TABLESPACE_NOT_FOUND: return(HA_ERR_TABLESPACE_MISSING);
case DB_TOO_BIG_RECORD: { /* If prefix is true then a 768-byte prefix is stored locallyforBLOBfields.Refertodict_table_get_format().
We limit max record size to 16k for 64k page size. */ bool prefix = !DICT_TF_HAS_ATOMIC_BLOBS(flags); bool comp = !!(flags & DICT_TF_COMPACT);
ulint free_space = page_get_free_space_of_empty(comp) / 2;
my_printf_error(ER_TOO_BIG_ROWSIZE, "Row size too large (> " ULINTPF "). Changing some columns " "to TEXT or BLOB %smay help. In current row " "format, BLOB prefix of %d bytes is stored inline.",
MYF(0),
free_space,
prefix
? "or using ROW_FORMAT=DYNAMIC or" " ROW_FORMAT=COMPRESSED "
: "",
prefix
? DICT_MAX_FIXED_COL_LEN
: 0); return(HA_ERR_TO_BIG_ROW);
}
case DB_TOO_BIG_INDEX_COL:
my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0),
(ulong) DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(flags)); return(HA_ERR_INDEX_COL_TOO_LONG);
case DB_LOCK_TABLE_FULL: /* Since we rolled back the whole transaction, we must tellitalsotoMySQLsothatMySQLknowstoemptythe
cached binlog for this transaction */
if (thd) {
thd->mark_transaction_to_rollback(true);
}
return(HA_ERR_LOCK_TABLE_FULL);
case DB_FTS_INVALID_DOCID: return(HA_FTS_INVALID_DOCID); case DB_FTS_EXCEED_RESULT_CACHE_LIMIT: return(HA_ERR_OUT_OF_MEM); case DB_TOO_MANY_CONCURRENT_TRXS: return(HA_ERR_TOO_MANY_CONCURRENT_TRXS); case DB_UNSUPPORTED: return(HA_ERR_UNSUPPORTED); case DB_INDEX_CORRUPT: return(HA_ERR_INDEX_CORRUPT); case DB_UNDO_RECORD_TOO_BIG: return(HA_ERR_UNDO_REC_TOO_BIG); case DB_OUT_OF_MEMORY: return(HA_ERR_OUT_OF_MEM); case DB_TABLESPACE_EXISTS: return(HA_ERR_TABLESPACE_EXISTS); case DB_TABLESPACE_DELETED: return(HA_ERR_TABLESPACE_MISSING); case DB_IDENTIFIER_TOO_LONG: return(HA_ERR_INTERNAL_ERROR); case DB_TABLE_CORRUPT: return(HA_ERR_TABLE_CORRUPT); case DB_FTS_TOO_MANY_WORDS_IN_PHRASE: return(HA_ERR_FTS_TOO_MANY_WORDS_IN_PHRASE); case DB_COMPUTE_VALUE_FAILED: return(HA_ERR_GENERIC); // impossible
}
}
/*************************************************************//**
Prints info of a THD object (== user session thread) to the given file. */ void
innobase_mysql_print_thd( /*=====================*/
FILE* f, /*!< in: output stream */
THD* thd) /*!< in: MySQL THD object */
{ char buffer[3072];
/******************************************************************//**
Get the variable length bounds of the given character set. */ staticvoid
innobase_get_cset_width( /*====================*/
ulint cset, /*!< in: MySQL charset-collation code */ unsigned*mbminlen, /*!< out: minimum length of a char (in bytes) */ unsigned*mbmaxlen) /*!< out: maximum length of a char (in bytes) */
{
CHARSET_INFO* cs;
ut_ad(cset <= MAX_CHAR_COLL_NUM);
ut_ad(mbminlen);
ut_ad(mbmaxlen);
/*********************************************************************//**
Compute the mbminlen and mbmaxlen members of a data type structure. */ void
dtype_get_mblen( /*============*/
ulint mtype, /*!< in: main type */
ulint prtype, /*!< in: precise type (and collation) */ unsigned*mbminlen, /*!< out: minimum length of a
multi-byte character */ unsigned*mbmaxlen) /*!< out: maximum length of a
multi-byte character */
{ if (dtype_is_string_type(mtype)) {
innobase_get_cset_width(dtype_get_charset_coll(prtype),
mbminlen, mbmaxlen);
ut_ad(*mbminlen <= *mbmaxlen);
ut_ad(*mbminlen < DATA_MBMAX);
ut_ad(*mbmaxlen < DATA_MBMAX);
} else {
*mbminlen = *mbmaxlen = 0;
}
}
/******************************************************************//**
Converts an identifier to UTF-8. */ void
innobase_convert_from_id( /*=====================*/
CHARSET_INFO* cs, /*!< in: the 'from' character set */ char* to, /*!< out: converted identifier */ constchar* from, /*!< in: identifier to convert */
ulint len) /*!< in: length of 'to', in bytes */
{
uint errors;
strconvert(cs, from, FN_REFLEN, system_charset_info, to, (uint) len, &errors);
}
/******************************************************************//**
Compares NUL-terminated UTF-8 strings case insensitively. The
second string contains wildcards.
@return0if a match is found, 1ifnot */ static int
innobase_wildcasecmp( /*=================*/ constchar* a, /*!< in: string to compare */ constchar* b) /*!< in: wildcard string to compare */
{ return(wild_case_compare(system_charset_info, a, b));
}
/** Strip dir name from a full path name and return only the file name @param[in]path_namefullpathname
@return file name or "null" if no file name */ constchar*
innobase_basename( constchar* path_name)
{ constchar* name = base_name(path_name);
/*********************************************************************//**
Wrapper around MySQL's copy_and_convert function.
@return number of bytes copied to 'to' */ static
ulint
innobase_convert_string( /*====================*/ void* to, /*!< out: converted string */
ulint to_length, /*!< in: number of bytes reserved
for the converted string */
CHARSET_INFO* to_cs, /*!< in: character set to convert to */ constvoid* 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,
(constchar*) from, (uint32) from_length, from_cs,
errors));
}
/*******************************************************************//**
Formats the raw data in "data" (in InnoDB on-disk format) that is of
type DATA_(CHAR|VARCHAR|MYSQL|VARMYSQL) using"charset_coll"and writes
the result to "buf". The result is converted to "system_charset_info". Not more than "buf_size" bytes are written to "buf".
The result is always NUL-terminated (provided buf_size > 0) and the
number of bytes that were written to "buf" is returned (including the
terminating NUL).
@return number of bytes that were written */
ulint
innobase_raw_format( /*================*/ constchar* 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;
/*********************************************************************//**
Compute the next autoinc value.
For MySQL replication the autoincrement values can be partitioned among
the nodes. The offset is the start or origin of the autoincrement value for a particular node. For n nodes the increment will be n and the offset
will be in the interval [1, n]. The formula tries to allocate the next
value for a particular node.
Note: This function is also called with increment set to the number of
values we want to reserve for multi-value inserts e.g.,
INSERT INTO T VALUES(), (), ();
innobase_next_autoinc() will be called with increment set to 3 where
autoinc_lock_mode != TRADITIONAL because we want to reserve 3 values for
the multi-value INSERT above.
@return the next value */
ulonglong
innobase_next_autoinc( /*==================*/
ulonglong current, /*!< in: Current value */
ulonglong need, /*!< in: count of values needed */
ulonglong step, /*!< in: AUTOINC increment step */
ulonglong offset, /*!< in: AUTOINC offset */
ulonglong max_value) /*!< in: max value for type */
{
ulonglong next_value;
ulonglong block;
/* Should never be 0. */
ut_a(need > 0);
ut_a(step > 0);
ut_a(max_value > 0);
/* Weneedtocalculatethe"block"valueequaltotheproduct "step*need".However,whencalculatingthisproduct,aninteger overflowcanoccur,sowecannotsimplyusetheusualmultiplication operation.Thesnippetbelowcalculatestheproductoftwonumbers anddetectsanunsignedintegeroverflow:
*/ unsignedint m= my_nlz(need); unsignedint n= my_nlz(step); if (m + n <= 8 * sizeof(ulonglong) - 2) { // The bit width of the original values is too large, // therefore we are guaranteed to get an overflow. goto overflow;
}
block = need * (step >> 1); if ((longlong) block < 0) { goto overflow;
}
block += block; if (step & 1) {
block += need; if (block < need) { goto overflow;
}
}
/* Check for overflow. Current can be > max_value if the value isinrealityanegativevalue.Also,thevisualstudiocompiler convertslargedoublevalues(whichhypotheticallycanthenbe passedhereasthevaluesofthe"current"parameter)automatically
into unsigned long long datatype maximum value: */ if (current > max_value) { goto overflow;
}
/* According to MySQL documentation, if the offset is greater than
the step then the offset is ignored. */ if (offset > step) {
offset = 0;
}
/*********************************************************************//**
Initializes some fields in an InnoDB transaction object. */ static void
innobase_trx_init( /*==============*/
THD* thd, /*!< in: user thread handle */
trx_t* trx) /*!< in/out: InnoDB transaction handle */
{
DBUG_ENTER("innobase_trx_init");
DBUG_ASSERT(thd == trx->mysql_thd);
/* Ensure that thd_lock_wait_timeout(), which may be called whileholdinglock_sys.latch,bylock_rec_enqueue_waiting(), willnotendupacquiringLOCK_global_system_variablesin
intern_sys_var_ptr(). */
(void) THDVAR(thd, lock_wait_timeout);
/*********************************************************************//**
Note that a transaction has been registered with MySQL.
@returntrueif transaction is registered with MySQL 2PC coordinator */ staticinline bool
trx_is_registered_for_2pc( /*======================*/ const trx_t* trx) /* in: transaction */
{ return(trx->is_registered == 1);
}
/*********************************************************************//**
Note that a transaction has been deregistered. */ staticinline void
trx_deregister_from_2pc( /*====================*/
trx_t* trx) /* in: transaction */
{
trx->is_registered= false;
trx->active_commit_ordered= false;
trx->active_prepare= false;
trx->commit_lsn= 0;
}
/* In the autocommit mode there is no sense to set a savepoint (unlessweareinsub-statement),soSQLlayerensuresthat
this method is never called in such situation. */
trx_t *trx= check_trx_exists(thd);
/* Cannot happen outside of transaction */
DBUG_ASSERT(trx_is_registered_for_2pc(trx));
switch (UNIV_EXPECT(trx->state, TRX_STATE_ACTIVE)) { default:
ut_ad("invalid state" == 0);
DBUG_RETURN(HA_ERR_NO_SAVEPOINT); case TRX_STATE_NOT_STARTED:
trx_start_if_not_started_xa(trx, false); /* fall through */ case TRX_STATE_ACTIVE: const undo_no_t savept{trx->undo_no};
*static_cast<undo_no_t*>(savepoint)= savept;
trx->last_stmt_start= savept;
trx->end_bulk_insert();
if (trx->fts_trx)
fts_savepoint_take(trx->fts_trx, savepoint);
@return0always
*/ staticint innobase_close_connection(THD *thd) noexcept
{ if (auto trx= thd_to_trx(thd))
{
thd_set_ha_data(thd, innodb_hton_ptr, nullptr); switch (trx->state) { case TRX_STATE_ABORTED:
trx->state= TRX_STATE_NOT_STARTED; /* fall through */ case TRX_STATE_NOT_STARTED:
ut_ad(!trx->id);
trx->will_lock= false; break; default:
ut_ad("invalid state" == 0); return0; case TRX_STATE_PREPARED: if (trx->has_logged_persistent())
{
trx->disconnect_prepared(); return0;
} /* fall through */ case TRX_STATE_ACTIVE: /* If we had reserved the auto-inc lock for some table (if wecomeheretorollbackthelatestSQLstatement)we
release it now before a possibly lengthy rollback */
lock_unlock_table_autoinc(trx);
trx_rollback_for_mysql(trx);
}
trx_deregister_from_2pc(trx);
trx->free();
DEBUG_SYNC(thd, "innobase_connection_closed");
} return0;
}
/** XAROLLBACKafterXAPREPARE
@paramxidX/OpenXAtransactionidentification
@retval0ifthetransactionwasfoundandrolledback @retvalXAER_NOTAifnosuchtransactionexists @retvalXAER_RMFAILifInnoDBisinread-onlymode
*/ staticint innobase_rollback_by_xid(XID *xid) noexcept
{ if (high_level_read_only || recv_sys.rpo) return XAER_RMFAIL; if (trx_t *trx= trx_get_trx_by_xid(xid))
{ /* Lookup by xid clears the transaction xid.
For wsrep we clear it below. */
ut_ad(trx->xid.is_null() || wsrep_is_wsrep_xid(&trx->xid));
trx->xid.null();
trx_deregister_from_2pc(trx);
THD* thd= trx->mysql_thd;
dberr_t err= trx_rollback_for_mysql(trx);
trx->free(); return convert_error_code_to_mysql(err, 0, thd);
} return XAER_NOTA;
}
Index preference, if set to YES|NO, will override table preference. */
ut_ad(!option_struct || option_struct->adaptive_hash_index <= TABLE_HINT_NO); const uint8_t table_ahi= option_struct
? uint8_t((option_struct->adaptive_hash_index + 1) % 3)
: uint8_t{1};
ut_ad(table_ahi <= dict_index_t::ahi::AHI_INDEX_PREFER_ENABLED);
/* Check if we can safely access InnoDB's ha_index_option_struct. ForpartitionedInnoDBtables,db_type()returnspartition_hton, soweneedtocheckdefault_part_pluginfortheunderlyingengine. Forwrapperengineslikemroonga,wemustNOTaccessInnoDB's
structure as it has a different memory layout. */ bool is_innodb_table= table->s->db_type() == innodb_hton_ptr; #ifdef WITH_PARTITION_STORAGE_ENGINE if (!is_innodb_table && table->s->default_part_plugin)
is_innodb_table=
plugin_hton(table->s->default_part_plugin) == innodb_hton_ptr; #endif
/* In case there is no PRIMARY KEY or UNIQUE INDEX on NOT NULL columns,therewillbeGEN_CLUST_INDEX(DB_ROW_ID).Defaulttothe tableoptionforit.IfaPRIMARYKEYisdefined,thisdefault
value may be updated in the loop below. */ auto* def_search_info=
&UT_LIST_GET_FIRST(innodb_table->indexes)->search_info; for (auto i= table->s->keys; i--; )
{ const KEY &key= table->s->key_info[i];
dict_index_t *index=
dict_table_get_index_on_name(innodb_table, key.name.str); if (!index || !index->n_uniq) continue; /* FTS indexes have n_uniq == 0; AHI is not applicable */
const uint16_t n_uniq= index->n_uniq; /* Clamp fields to the number of unique index fields */ if (UNIV_UNLIKELY(fields != opt_max && fields > n_uniq))
fields= n_uniq;
ut_ad(fields == opt_max || fields <= n_uniq); /* If all unique fields are included, force bytes to 0 */ if (UNIV_UNLIKELY(fields != opt_max && fields >= n_uniq))
bytes= 0; /* If bytes is set and fields is not, force fields to 0 to
disambiguate which field are the bytes referring to */ if (UNIV_UNLIKELY(fields == opt_max && bytes != opt_max))
fields= 0; /* If fields < n_uniq and bytes != opt_max, there is no need
to clamp bytes, as rec_fold() will already do so */
/* fields==0 + bytes==0 is useless */ if (UNIV_UNLIKELY(index_ahi && fields == 0 && bytes == 0))
index_ahi= 0; /* Force disable AHI */ /* Use index preference if set, otherwise use table preference */ const uint8_t mask= uint8_t(0 - (index_ahi & 1)); /* mask == 0xFF if index_ahi == 1, indicates index preference unset */ const uint8_t ahi= uint8_t((table_ahi & mask) | (index_ahi & ~mask));
ut_ad(ahi <= dict_index_t::ahi::AHI_INDEX_PREFER_ENABLED);
static_assert((uint64_t(1) << 63) > max_complete_fields, "Cannot use highest bit as unused flag");
static_assert((uint64_t(1) << 63) > max_bytes_from_incomplete_field, "Cannot use highest bit as unused flag");
index->search_info.set_enabled_fixed_mask(
dict_index_t::ahi::preference{ahi},
(~fields >> 63) & 1, /* fields != opt_max */
(~bytes >> 63) & 1, /* bytes != opt_max */
(right | (right >> 1)) & 1, /* right != TABLE_HINT_DEFAULT */
uint8_t(fields),
uint16_t(bytes),
(right >> 1) & 1/* right == TABLE_HINT_NO */
);
if (def_search_info == &index->search_info)
def_search_info= nullptr;
} if (def_search_info)
def_search_info->set_enabled_fixed_mask(
dict_index_t::ahi::preference{table_ahi}, false, false, false, 0, 0, false);
} #endif
/*********************************************************************//**
Copy table flags from MySQL's HA_CREATE_INFO into an InnoDB table object.
Those flags are stored in .frm file and end up in the MySQL table object,
but are frequently used inside InnoDB so we keep their copies into the
InnoDB table object. */ static void
innobase_copy_frm_flags_from_create_info( /*=====================================*/
dict_table_t* innodb_table, /*!< in/out: InnoDB table */ const HA_CREATE_INFO* create_info, /*!< in: create info */ const TABLE* table) /*!< in: MariaDB table */
{ if (innodb_copy_stat_flags(innodb_table, create_info->table_options,
create_info->stats_auto_recalc, false))
{
innodb_table->stats_sample_pages= create_info->stats_sample_pages; #ifdef BTR_CUR_HASH_ADAPT
innodb_ahi_enable(innodb_table, create_info->option_struct, table); #endif
}
}
/*********************************************************************//**
Updates the user_thd field in a handle and also allocates a new InnoDB
transaction handle if needed, and updates the transaction fields in the
m_prebuilt struct. */ void
ha_innobase::update_thd( /*====================*/
THD* thd) /*!< in: thd to use the handle */
{
DBUG_ENTER("ha_innobase::update_thd");
DBUG_PRINT("ha_innobase::update_thd", ("user_thd: %p -> %p",
m_user_thd, thd));
/* The table should have been opened in ha_innobase::open(). */
DBUG_ASSERT(m_prebuilt->table->get_ref_count() > 0);
/*********************************************************************//**
Updates the user_thd field in a handle and also allocates a new InnoDB
transaction handle if needed, and updates the transaction fields in the
m_prebuilt struct. */
/*********************************************************************//**
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. */ staticinline void
innobase_register_trx( /*==================*/
handlerton* hton, /* in: Innobase handlerton */
THD* thd, /* in: MySQL thd (connection) object */
trx_t* trx) /* in: transaction to register */
{
ut_ad(!trx->active_commit_ordered);
ut_ad(!trx->active_prepare); const trx_id_t trx_id= trx->id;
trans_register_ha(thd, false, hton, trx_id);
if (!trx->is_registered)
{
trx->is_registered= true; if (thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
trans_register_ha(thd, true, hton, trx_id);
}
}
/* BACKGROUND INFO: HOW THE MYSQL QUERY CACHE WORKS WITH INNODB ------------------------------------------------------------
/** Check if mysql can allow the transaction to read from/store to thequerycache. @param[in]tabletableobject @param[in]trxtransactionobject
@return whether the storing or retrieving from the query cache is permitted */
TRANSACTIONAL_TARGET staticbool innobase_query_caching_table_check_low(
dict_table_t* table, trx_t* trx)
{ /* The following conditions will decide the query cache retrievalorstoringinto:
if (trx->read_view.is_open() && trx->read_view.low_limit_id() < inv) { returnfalse;
}
#if !defined NO_ELISION && !defined SUX_LOCK_GENERIC if (xbegin()) { if (table->lock_mutex_is_locked())
xabort(); auto len = UT_LIST_GET_LEN(table->locks);
xend(); return len == 0;
} #endif
table->lock_shared_lock(); auto len= UT_LIST_GET_LEN(table->locks);
table->lock_shared_unlock(); return len == 0;
}
/** Checks if MySQL at the moment is allowed for this table to retrieve a consistentreadresult,orstoreittothequerycache. @param[in,out]trxtransaction @param[in]norm_nameconcatenationofdatabasename, '/'char,tablename
@return whether storing or retrieving from the query cache is permitted */ staticbool innobase_query_caching_table_check(
trx_t* trx, constchar* norm_name)
{
dict_table_t* table = dict_table_open_on_name(
norm_name, false, DICT_ERR_IGNORE_FK_NOKEY);
if (table == NULL) { returnfalse;
}
/* Start the transaction if it is not started yet */
trx_start_if_not_started(trx, false);
if (allow) { /* If the isolation level is high, assign a read view for the
transaction if it does not yet have one */
if (trx->isolation_level >= TRX_ISO_REPEATABLE_READ
&& !srv_read_only_mode
&& !trx->read_view.is_open()) {
/* Start the transaction if it is not started yet */
trx_start_if_not_started(trx, false);
trx->read_view.open(trx);
}
}
return allow;
}
/******************************************************************//**
The MySQL query cache uses this to check from InnoDB if the query cache at
the moment is allowed to operate on an InnoDB table. The SQL query must
be a non-locking SELECT.
The query cache is allowed to operate on certain query only ifthis function
returns TRUEfor 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, andthis function will reserve the trx_sys.mutex.
@returnTRUEif permitted, FALSEifnot; 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 storearesulttothequerycacheor
retrieve it */ constchar* full_name, /*!< in: normalized path to the table */
uint full_name_len, /*!< in: length of the normalized path
to the table */
ulonglong *)
{ char norm_name[1000];
trx_t* trx = check_trx_exists(thd);
ut_a(full_name_len < 999);
if (trx->isolation_level == TRX_ISO_SERIALIZABLE) { /* In the SERIALIZABLE mode we add LOCK IN SHARE MODE to every
plain SELECT if AUTOCOMMIT is not on. */
return(false);
}
if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)
&& trx->n_mysql_tables_in_use == 0) { /* We are going to retrieve the query result from the query cache.Thiscannotbeastoreoperationtothequerycache becausethenMySQLwouldhavelocksontablesalready.
Wecanimagineweinstantaneouslyserializethisconsistent readtrxtothecurrenttrxidcounter.Iftrx2wouldhave changedthetablesofaqueryresultstoredinthecache,and trx2wouldhavealreadycommitted,makingtheresultobsolete, thentrx2wouldhavealreadyinvalidatedthecache.Thuswe
can trust the result in the cache is ok for this query. */
return(true);
}
/* Normalize the table name to InnoDB format */
normalize_table_name(norm_name, sizeof(norm_name), full_name);
/*****************************************************************//**
Invalidates the MySQL query cache for the table. */ void
innobase_invalidate_query_cache( /*============================*/
trx_t* trx, /*!< in: transaction which
modifies the table */ constchar* full_name) /*!< in: concatenation of databasename,pathseparator, tablename,nullcharNUL; NOTEthatinWindowsthisis
always in LOWER CASE! */
{ /* Note that the query cache mutex is just above the trx_sys.mutex.
The caller of this function must not have latches of a lower rank. */
// Extract the database name.
key_ptr= strchr(full_name, '/');
DBUG_ASSERT(key_ptr != NULL); // Database name should be present
size_t dbname_len= size_t(key_ptr - full_name);
memcpy(db_name, full_name, dbname_len);
db_name[dbname_len]= '\0';
/* Construct the key("db-name\0table$name\0") for the query cache using thepathname("db@002dname\0table@0024name\0")ofthetableinits
canonical form. */
dbname_len = filename_to_tablename(db_name, qcache_key_name, sizeof(qcache_key_name));
tabname_len = filename_to_tablename(++key_ptr,
(qcache_key_name + dbname_len + 1), sizeof(qcache_key_name) -
dbname_len - 1);
/* Argument TRUE below means we are using transactions */
mysql_query_cache_invalidate4(trx->mysql_thd,
qcache_key_name,
uint(dbname_len + tabname_len + 2), TRUE);
}
/** Quote a standard SQL identifier like index or column name. @param[in]fileoutputstream @param[in]trxInnoDBtransaction,orNULL
@param[in] id identifier to quote */ void
innobase_quote_identifier(
FILE* file, const trx_t* trx, constchar* id)
{
std::string str = innobase_quote_identifier(trx, id);
fputs(str.c_str(), file);
}
/** Quote a standard SQL identifier @param[in]trxInnoDBtransaction,orNULL @param[in]ididentifiertoquote @returnquotedidentifier
Assumes the identifier in utf8mb4 character set (cf. append_identifier()) */
std::string
innobase_quote_identifier( /*======================*/ const trx_t* trx, constchar* id)
{
std::string quoted_identifier; constint q = trx != NULL && trx->mysql_thd != NULL
? get_quote_char_for_identifier(trx->mysql_thd, id, strlen(id))
: '`';
if (q == EOF) {
quoted_identifier.append(id);
} else {
quoted_identifier += char(q); while (int c = *id++) { if (c == q) {
quoted_identifier += char(c);
}
quoted_identifier += char(c);
}
quoted_identifier += char(q);
}
return (quoted_identifier);
}
/** Convert a table name to the MySQL system_charset_info (UTF-8) andquoteit. @param[out]bufbufferforconvertedidentifier @param[in]buflenlengthofbuf,inbytes @param[in]ididentifiertoconvert @param[in]idlenlengthofid,inbytes @param[in]thdMySQLconnectionthread,orNULL
@return pointer to the end of buf */ static char*
innobase_convert_identifier( char* buf,
ulint buflen, constchar* id,
ulint idlen,
THD* thd)
{ constchar* s = id;
/* Decode the table name. The MySQL function expects aNUL-terminatedstring.Theinputandoutputstrings
buffers must not be shared. */
ut_a(idlen <= MAX_TABLE_NAME_LEN);
memcpy(nz, id, idlen);
nz[idlen] = 0;
/*****************************************************************//**
Convert a table name to the MySQL system_charset_info (UTF-8).
@return pointer to the end of buf */ char*
innobase_convert_name( /*==================*/ char* buf, /*!< out: buffer for converted identifier */
ulint buflen, /*!< in: length of buf, in bytes */ constchar* id, /*!< in: table name to convert */
ulint idlen, /*!< in: length of id, in bytes */
THD* thd) /*!< in: MySQL connection thread, or NULL */
{ char* s = buf; constchar* bufend = buf + buflen;
/* Print the database name and table name separately. */
s = innobase_convert_identifier(s, ulint(bufend - s),
id, ulint(slash - id), thd); if (s < bufend) {
*s++ = '.';
s = innobase_convert_identifier(s, ulint(bufend - s),
slash + 1, idlen
- ulint(slash - id) - 1,
thd);
}
return(s);
}
/*****************************************************************//**
A wrapper function of innobase_convert_name(), convert a table name
to the MySQL system_charset_info (UTF-8) and quote it if needed.
@return pointer to the end of buf */ void
innobase_format_name( /*==================*/ char* buf, /*!< out: buffer for converted identifier */
ulint buflen, /*!< in: length of buf, in bytes */ constchar* name) /*!< in: table name to format */
{ char* bufend;
/**********************************************************************//**
Determines if the currently running transaction has been interrupted.
@returntrueif interrupted */ bool
trx_is_interrupted( /*===============*/ const trx_t* trx) /*!< in: transaction */
{ return(trx && trx->mysql_thd && thd_kill_level(trx->mysql_thd));
}
/**************************************************************//**
Resets some fields of a m_prebuilt struct. The template is used in fast
retrieval of just those column values MySQL needs in its processing. */ void
ha_innobase::reset_template(void) /*=============================*/
{
ut_ad(m_prebuilt->magic_n == ROW_PREBUILT_ALLOCATED);
ut_ad(m_prebuilt->magic_n2 == m_prebuilt->magic_n);
/* Force table to be freed in close_thread_table(). */
DBUG_EXECUTE_IF("free_table_in_fts_query", if (m_prebuilt->in_fts_query) {
table->mark_table_for_reopen();
}
);
/* Reset index condition pushdown state. */ if (m_prebuilt->idx_cond) {
m_prebuilt->idx_cond = NULL;
m_prebuilt->idx_cond_n_cols = 0; /* Invalidate m_prebuilt->mysql_template
in ha_innobase::write_row(). */
m_prebuilt->template_type = ROW_MYSQL_NO_TEMPLATE;
} if (m_prebuilt->pk_filter) {
m_prebuilt->pk_filter = NULL;
m_prebuilt->template_type = ROW_MYSQL_NO_TEMPLATE;
}
}
/*****************************************************************//**
Call this when you have opened a new table handle in HANDLER, before you
call index_read_map() etc. Actually, we can let the cursor stay open even
over a transaction commit! Then you should call this before every operation,
fetch next etc. This function inits the necessary things even after a
transaction commit. */
void
ha_innobase::init_table_handle_for_HANDLER(void) /*============================================*/
{ /* If current thd does not yet have a trx struct, create one. Ifthecurrenthandledoesnotyethaveam_prebuiltstruct,create one.Updatethetrxpointersinthem_prebuiltstruct.Normally
this operation is done in external_lock. */
update_thd(ha_thd());
/* Initialize the m_prebuilt struct much like it would be inited in
external_lock */
/* If the transaction is not started yet, start it */
static MYSQL_SYSVAR_SIZE_T(buffer_pool_size, buf_pool.size_in_bytes_requested,
PLUGIN_VAR_RQCMDARG, "The size of the memory buffer InnoDB uses to cache data" " and indexes of its tables",
nullptr, innodb_buffer_pool_size_update, 128U << 20, 2U << 20,
size_t(-ssize_t(innodb_buffer_pool_extent_size)), 1U << 20);
static MYSQL_SYSVAR_UINT(log_write_ahead_size, log_sys.write_size,
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, "Redo log write size to avoid read-on-write; must be a power of two",
nullptr, nullptr, 512, 512, log_sys.WRITE_SIZE_MAX, 1);
#ifdef BTR_CUR_HASH_ADAPT staticvoid innodb_adaptive_hash_index_update(THD*, st_mysql_sys_var*, void*, constvoid *save) noexcept
{
ulong option; /* Prevent a possible deadlock with innobase_fts_load_stopword() */
mysql_mutex_unlock(&LOCK_global_system_variables);
staticvoid innodb_adaptive_hash_index_cells_update(THD*, st_mysql_sys_var*, void*, constvoid *save)
noexcept
{ /* Prevent a possible deadlock with innobase_fts_load_stopword() */
mysql_mutex_unlock(&LOCK_global_system_variables);
btr_search.resize(*static_cast<const uint*>(save));
mysql_mutex_lock(&LOCK_global_system_variables);
}
static MYSQL_SYSVAR_UINT(adaptive_hash_index_cells, btr_search.n_cells,
PLUGIN_VAR_RQCMDARG, "Number of adaptive hash table cells in each partition;" " 16381 at start defaults to being derived from innodb_buffer_pool_size",
nullptr, innodb_adaptive_hash_index_cells_update, 16381, 16381, sizeof(size_t) < 8 ? (1U << 30) - 1 : UINT_MAX, 0); #endif/* BTR_CUR_HASH_ADAPT */
/****************************************************************//**
Gives the file extension of an InnoDB single-table tablespace. */ staticconstchar* ha_innobase_exts[] = {
dot_ext[IBD],
dot_ext[ISL],
NullS
};
/** Determine if system-versioned data was modified by the transaction. @param[in,out]thdcurrentsession @param[out]trx_idtransactionstartID @returntransactioncommitID
@retval 0 if no system-versioned data was affected by the transaction */ static ulonglong innodb_prepare_commit_versioned(THD* thd, ulonglong *trx_id)
{ if (trx_t *trx= thd_to_trx(thd))
{
*trx_id= trx->id; bool versioned= false;
for (auto &t : trx->mod_tables)
{ if (t.second.is_versioned())
{
DBUG_ASSERT(t.first->versioned_by_id());
DBUG_ASSERT(trx->rsegs.m_redo.rseg);
versioned= true; if (!trx->bulk_insert) break;
} if (t.second.is_bulk_insert())
{
ut_ad(trx->bulk_insert); if (t.second.write_bulk(t.first, trx)) return ULONGLONG_MAX;
}
}
my_printf_error(HA_ERR_UNSUPPORTED, "InnoDB: compression algorithm %s (%lu)" " is not available. Please, load the corresponding provider plugin.", flags,
page_compression_algorithms[compression_algorithm], compression_algorithm); return1;
}
/** Initial value of innodb_lsn_archived */ static uint64_t innodb_log_archive_start;
/** Initialize, validate and normalize the InnoDB startup parameters. @returnfailurecode @retval0onsuccess @retvalHA_ERR_OUT_OF_MEMwhenoutofmemory
@retval HA_ERR_INITIALIZATION when some parameters are out of range */ staticint innodb_init_params()
{
DBUG_ENTER("innodb_init_params");
if (innodb_buffer_pool_size < min)
{
sql_print_error("InnoDB: innodb_page_size=%lu requires " "innodb_buffer_pool_size >= %zu MiB current %zu MiB",
srv_page_size, min >> 20, innodb_buffer_pool_size >> 20);
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
if (!ut_is_2pow(log_sys.write_size))
{
sql_print_error("InnoDB: innodb_log_write_ahead_size=%u" " is not a power of two",
log_sys.write_size);
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
/* Set InnoDB initialization parameters according to the values
read from MySQL .cnf file */
/* The default dir for data files is the datadir of MySQL */
srv_data_home= innobase_data_home_dir
? innobase_data_home_dir
: const_cast<char*>(fil_path_to_mysql_datadir); #ifdef WITH_WSREP /* If we use the wsrep API, then we need to tell the server
the path to the data files (for passing it to the SST scripts): */
wsrep_set_data_home_dir(srv_data_home); #endif/* WITH_WSREP */
/* Check that the value of system variable innodb_page_size was setcorrectly.Itsvaluewasputintosrv_page_size.Ifvalid,
return the associated srv_page_size_shift. */
srv_sys_space.set_space_id(TRX_SYS_SPACE); /* Temporary tablespace is in full crc32 format. */
srv_tmp_space.set_flags(FSP_FLAGS_FCRC32_MASK_MARKER |
FSP_FLAGS_FCRC32_PAGE_SSIZE());
switch (srv_checksum_algorithm) { case SRV_CHECKSUM_ALGORITHM_FULL_CRC32: case SRV_CHECKSUM_ALGORITHM_STRICT_FULL_CRC32:
srv_sys_space.set_flags(srv_tmp_space.flags()); break; default:
srv_sys_space.set_flags(FSP_FLAGS_PAGE_SSIZE());
}
srv_sys_space.set_path(srv_data_home);
if (!srv_sys_space.parse_params(innobase_data_file_path, true))
{
sql_print_error("InnoDB: Unable to parse innodb_data_file_path=%s",
innobase_data_file_path);
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
srv_tmp_space.set_path(srv_data_home);
if (!srv_tmp_space.parse_params(innobase_temp_data_file_path, false))
{
sql_print_error("InnoDB: Unable to parse innodb_temp_data_file_path=%s",
innobase_temp_data_file_path);
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
/* Perform all sanity check before we take action of deleting files*/ if (srv_sys_space.intersection(&srv_tmp_space))
{
sql_print_error("innodb_temporary and innodb_system" " file names seem to be the same.");
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
/* ------------ UNDO tablespaces files ---------------------*/ if (!srv_undo_dir)
srv_undo_dir= const_cast<char*>(fil_path_to_mysql_datadir);
if (strchr(srv_undo_dir, ';'))
{
sql_print_error("syntax error in innodb_undo_directory");
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
if (!srv_log_group_home_dir)
srv_log_group_home_dir= const_cast<char*>(fil_path_to_mysql_datadir);
if (strchr(srv_log_group_home_dir, ';'))
{
sql_print_error("syntax error in innodb_log_group_home_dir");
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
/* Check that interdependent parameters have sane values. */ if (srv_max_buf_pool_modified_pct < srv_max_dirty_pages_pct_lwm)
{
sql_print_warning("InnoDB: innodb_max_dirty_pages_pct_lwm" " cannot be set higher than" " innodb_max_dirty_pages_pct.\n" "InnoDB: Setting" " innodb_max_dirty_pages_pct_lwm to %lf\n",
srv_max_buf_pool_modified_pct);
srv_max_dirty_pages_pct_lwm = srv_max_buf_pool_modified_pct;
}
if (srv_max_io_capacity == SRV_MAX_IO_CAPACITY_DUMMY_DEFAULT)
{ if (srv_io_capacity >= SRV_MAX_IO_CAPACITY_LIMIT / 2) /* Avoid overflow. */
srv_max_io_capacity= SRV_MAX_IO_CAPACITY_LIMIT; else /* The user has not set the value. We should set it based on
innodb_io_capacity. */
srv_max_io_capacity= std::max(2 * srv_io_capacity, 2000UL);
} elseif (srv_max_io_capacity < srv_io_capacity)
{
sql_print_warning("InnoDB: innodb_io_capacity cannot be set higher than" " innodb_io_capacity_max." "Setting innodb_io_capacity=%lu", srv_max_io_capacity);
srv_io_capacity= srv_max_io_capacity;
}
if (innobase_open_files > open_files_limit)
{
sql_print_warning("InnoDB: innodb_open_files %lu" " should not be greater than the open_files_limit %lu",
innobase_open_files, open_files_limit); if (innobase_open_files > tc_size)
innobase_open_files= tc_size;
}
const size_t min_open_files_limit= srv_undo_tablespaces +
srv_sys_space.m_files.size() + srv_tmp_space.m_files.size() + 1; if (min_open_files_limit > innobase_open_files)
{
sql_print_warning("InnoDB: innodb_open_files=%lu is not greater " "than the number of system tablespace files, " "temporary tablespace files, " "innodb_undo_tablespaces=%u; adjusting " "to innodb_open_files=%zu",
innobase_open_files, srv_undo_tablespaces,
min_open_files_limit);
innobase_open_files= ulong(min_open_files_limit);
}
if (!ut_is_2pow(fts_sort_pll_degree))
{
ulong n; for (n= 1; n < fts_sort_pll_degree; n<<= 1) {}
fts_sort_pll_degree= n;
}
if (innodb_flush_method == 1/* O_DSYNC */)
{
log_sys.log_write_through= true;
fil_system.write_through= true;
fil_system.buffered= false; #ifdefined __linux__ || defined _WIN32
log_sys.log_buffered= false; goto skip_buffering_tweak; #endif
} elseif (innodb_flush_method >= 4/* O_DIRECT */ &&
IF_WIN(innodb_flush_method < 8/* normal */, true))
{ /* O_DIRECT and similar settings do nothing */ if (innodb_flush_method == 5/* O_DIRECT_NO_FSYNC */ && buf_dblwr.use)
buf_dblwr.use= buf_dblwr.USE_FAST;
} #ifdef O_DIRECT elseif (srv_use_atomic_writes && my_may_have_atomic_write) /* If atomic writes are enabled, do the same as with
innodb_flush_method=O_DIRECT: retain the default settings */; #endif else
{
log_sys.log_write_through= false;
fil_system.write_through= false;
fil_system.buffered= true;
}
#ifdefined __linux__ || defined _WIN32 if (srv_flush_log_at_trx_commit == 2) /* Do not disable the file system cache if
innodb_flush_log_at_trx_commit=2. */
log_sys.log_buffered= true;
skip_buffering_tweak: #endif
log_sys.archived_lsn= innodb_log_archive_start;
if (recv_sys.recovery_start &&
log_sys.archived_lsn > recv_sys.recovery_start)
{
sql_print_error("InnoDB: innodb_log_archive_start=" LSN_PF " is after innodb_log_recovery_start=" LSN_PF,
log_sys.archived_lsn, recv_sys.recovery_start);
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
if (recv_sys.rpo && recv_sys.recovery_start > recv_sys.rpo)
{
sql_print_error("InnoDB: innodb_log_recovery_start=" LSN_PF " is after innodb_log_recovery_target=" LSN_PF,
recv_sys.recovery_start, recv_sys.rpo);
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
if (!tpool::supports_native_aio())
srv_use_native_aio= FALSE;
DBUG_RETURN(0);
}
/*********************************************************************//**
Setup costs factors for InnoDB to be able to approximate how many
ms different opperations takes. See cost functions in handler.h how
the different variables are used */
/* Do this as late as possible so server is fully starts up, sincewemightgetsomeinitialstatsifuserchoosetoturn
on some counters from start up */ if (innobase_enable_monitor_counter) {
innodb_enable_monitor_at_startup(
innobase_enable_monitor_counter);
}
/* Turn on monitor counters that are default on */
srv_mon_default_on();
/* Unit Tests */ #ifdef UNIV_ENABLE_UNIT_TEST_GET_PARENT_DIR
unit_test_os_file_get_parent_dir(); #endif/* UNIV_ENABLE_UNIT_TEST_GET_PARENT_DIR */
/*****************************************************************//**
Creates an InnoDB transaction structfor 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.
@return0 */ static int
innobase_start_trx_and_assign_read_view( /*====================================*/
THD* thd) /*!< in: MySQL thread handle of the user for
whom the transaction should be committed */
{
DBUG_ENTER("innobase_start_trx_and_assign_read_view");
/* Create a new trx struct for thd, if it does not yet have one */
trx_t* trx = check_trx_exists(thd);
/* The transaction should not be active yet, start it */
ut_ad(!trx->is_started());
trx_start_if_not_started_xa(trx, false);
/* Assign a read view if the transaction does not have one yet.
Skip this for the READ UNCOMMITTED isolation level. */
trx->isolation_level = innodb_isolation_level(thd) & 3;
if (trx->isolation_level != TRX_ISO_READ_UNCOMMITTED) {
trx->read_view.open(trx);
} else {
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
HA_ERR_UNSUPPORTED, "InnoDB: WITH CONSISTENT SNAPSHOT" " is ignored at READ UNCOMMITTED" " isolation level.");
}
/* Set the MySQL flag to mark that there is an active transaction */
Ifthebinarylogisnotenabled,orthetransaction isnotwrittentothebinarylog,thefilenamewill
be a NULL pointer. */
thd_binlog_pos(thd, &trx->mysql_log_file_name,
&trx->mysql_log_offset);
/* Don't do write + flush right now. For group commit
to work we want to do the flush later. */
trx->flush_log_later = true;
}
#ifdef WITH_WSREP /* If the transaction is not run in 2pc, we must assign wsrep
XID here in order to get it written in rollback segment. */ if (trx->is_wsrep()) {
trx->xid = *thd->get_xid();
} #endif/* WITH_WSREP */
/*****************************************************************//**
Perform the first, fast part of InnoDB commit.
Doing it in this call ensures that we get the same commit order here
as in binlog and any other participating transactional storage engines.
Note that we want to do as little as really needed here, as we run
under a global mutex. The expensive fsync() is done later, in
innobase_commit(), without a lock so group commit can take place.
Note also that this method can be called from a different thread than
the one handling the rest of the transaction. */ static void
innobase_commit_ordered( /*====================*/
THD* thd, /*!< in: MySQL thread handle of the user for whom
the transaction should be committed */ bool all) /*!< in: TRUE - commit transaction
FALSE - the current SQL statement ended */
{
trx_t* trx;
DBUG_ENTER("innobase_commit_ordered");
trx = check_trx_exists(thd);
if (!trx_is_registered_for_2pc(trx) && trx->is_started()) { /* We cannot throw error here; instead we will catch this error
again in innobase_commit() and report it from there. */
DBUG_VOID_RETURN;
}
/* commit_ordered is only called when committing the whole transaction
(or an SQL statement when autocommit is on). */
DBUG_ASSERT(all ||
(!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)));
/** Mark the end of a statement. @paramtrxtransaction
@return whether an error occurred */ staticbool end_of_statement(trx_t *trx) noexcept
{
ut_d(const trx_state_t trx_state{trx->state});
ut_ad(trx_state == TRX_STATE_ACTIVE || trx_state == TRX_STATE_NOT_STARTED);
if (trx->fts_trx)
fts_savepoint_laststmt_refresh(trx); if (trx->is_bulk_insert())
{ /* Allow a subsequent INSERT into an empty table
if !unique_checks && !foreign_key_checks. */
/* MDEV-25036 FIXME: we support buffered insert only for the first
insert statement */
trx->error_state= trx->bulk_insert_apply();
} else
{
trx->last_stmt_start= trx->undo_no;
trx->end_bulk_insert();
}
if (UNIV_LIKELY(trx->error_state == DB_SUCCESS)) returnfalse;
undo_no_t savept= 0;
trx->rollback(&savept); /* MariaDB will roll back the entire transaction. */
trx->clear_dml_bulk();
trx->last_stmt_start= 0; returntrue;
}
/*****************************************************************//**
Commits a transaction in an InnoDB database or marks an SQL statement
ended.
@return0or deadlock error if the transaction was aborted by another
higher priority transaction. */ static int
innobase_commit( /*============*/
THD* thd, /*!< in: MySQL thread handle of the userforwhomthetransactionshould
be committed */ bool commit_trx) /*!< in: true - commit transaction false-thecurrentSQLstatement
ended */
{
DBUG_ENTER("innobase_commit");
DBUG_PRINT("enter", ("commit_trx: %d", commit_trx));
DBUG_PRINT("trans", ("ending transaction"));
switch (UNIV_EXPECT(trx->state, TRX_STATE_ACTIVE)) { case TRX_STATE_ABORTED:
trx->state = TRX_STATE_NOT_STARTED; /* fall through */ case TRX_STATE_NOT_STARTED: break; default: case TRX_STATE_COMMITTED_IN_MEMORY: case TRX_STATE_PREPARED_RECOVERED:
ut_ad("invalid state" == 0); /* fall through */ case TRX_STATE_PREPARED:
ut_ad(commit_trx ||
!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT
| OPTION_BEGIN)); /* fall through */ case TRX_STATE_ACTIVE: /* Transaction is deregistered only in a commit or a rollback.Ifitisderegisteredweknowtherecannot beresourcestobefreedandwecouldreturn immediately.Forthetimebeing,weplaysafeanddo thecleanupthoughthereshouldbenothingtoclean
up. */ if (!trx_is_registered_for_2pc(trx)) {
sql_print_error("Transaction not registered" " for MariaDB 2PC," " but transaction is active");
}
}
if (commit_trx
|| (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
/* Run the fast part of commit if we did not already. */ if (!trx->active_commit_ordered) {
innobase_commit_ordered_2(trx, thd);
}
/* We were instructed to commit the whole transaction, or
this is an SQL statement end and autocommit is on */
/* At this point commit order is fixed and transaction is visibletoothers.Sowecanwakeupothercommitswaitingfor
this one, to allow then to group commit with us. */
thd->wakeup_subsequent_commits(0);
/* Now do a write + flush of logs. */
trx_commit_complete_for_mysql(trx);
trx_deregister_from_2pc(trx);
} else { /* We just mark the SQL statement ended and do not do a
transaction commit */
lock_unlock_table_autoinc(trx); if (UNIV_UNLIKELY(end_of_statement(trx))) {
DBUG_RETURN(1);
}
}
/* Reset the number AUTO-INC rows required */
trx->n_autoinc_rows = 0;
/* This is a statement level variable. */
trx->fts_next_doc_id = 0;
DBUG_RETURN(0);
}
/*****************************************************************//**
Rolls back a transaction or the latest SQL statement.
@return0or error number */ static int
innobase_rollback( /*==============*/
THD* thd, /*!< in: handle to the MySQL thread oftheuserwhosetransactionshould
be rolled back */ bool rollback_trx) /*!< in: TRUE - rollback entire transactionFALSE-rollbackthecurrent
statement only */
{
DBUG_ENTER("innobase_rollback");
DBUG_PRINT("trans", ("aborting transaction"));
if (!rollback_trx)
rollback_trx= !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN);
/* Reset the number AUTO-INC rows required */
trx->n_autoinc_rows= 0; /* This is a statement level variable. */
trx->fts_next_doc_id= 0;
const trx_state_t trx_state{trx->state}; switch (UNIV_EXPECT(trx_state, TRX_STATE_ACTIVE)) { case TRX_STATE_ABORTED: if (rollback_trx)
trx->state= TRX_STATE_NOT_STARTED; /* fall through */ case TRX_STATE_NOT_STARTED:
ut_ad(!trx->id);
trx->will_lock= false; if (rollback_trx)
trx_deregister_from_2pc(trx);
DBUG_RETURN(0); default: case TRX_STATE_COMMITTED_IN_MEMORY: case TRX_STATE_PREPARED_RECOVERED:
ut_ad("invalid state" == 0); /* fall through */ case TRX_STATE_PREPARED:
ut_ad(rollback_trx); /* fall through */ case TRX_STATE_ACTIVE: /* If we had reserved the auto-inc lock for some table (if wecomeheretorollbackthelatestSQLstatement)we
release it now before a possibly lengthy rollback */
lock_unlock_table_autoinc(trx);
#ifdef WITH_WSREP /* If trx was assigned wsrep XID in prepare phase and the trxisbeingrolledbackduetoBFabort,clearXIDinorder toavoidwritingittorollbacksegmentoutoforder.TheXID
will be reassigned when the transaction is replayed. */ if (rollback_trx || wsrep_is_wsrep_xid(&trx->xid))
trx->xid.null(); #endif/* WITH_WSREP */
dberr_t error; if (rollback_trx)
{
error= trx_rollback_for_mysql(trx);
trx_deregister_from_2pc(trx);
} else
{
ut_a(trx_state == TRX_STATE_ACTIVE);
ut_ad(!trx->is_autocommit_non_locking() || trx->read_only);
error= trx->rollback(&trx->last_stmt_start); if (trx->fts_trx)
{
fts_savepoint_rollback_last_stmt(trx);
fts_savepoint_laststmt_refresh(trx);
}
trx->last_stmt_start= trx->undo_no;
trx->end_bulk_insert();
}
DBUG_RETURN(convert_error_code_to_mysql(error, 0, trx->mysql_thd));
}
}
/** Invoke commit_checkpoint_notify_ha() on completed log flush requests. @parampendinglog_requests.start
@param lsn log_sys.get_flushed_lsn() */ staticvoid log_flush_notify_and_unlock(log_flush_request *pending, lsn_t lsn)
{
mysql_mutex_assert_owner(&log_requests.mutex);
ut_ad(pending == log_requests.start.load(std::memory_order_relaxed));
log_flush_request *entry= pending, *last= nullptr; /* Process the first requests that have been completed. Since thelistisnotnecessarilyinascendingorderofLSN,wemay misstonotifysomerequeststhathavealreadybeencompleted. Butthereisnoharmindelayingnotificationsforthoseabit. Andinpractise,thelistisunlikelytohavemorethanone elementanyway,becausetheredologwouldbeflushedevery
srv_flush_log_at_timeout seconds (1 by default). */ for (; entry && entry->lsn <= lsn; last= entry, entry= entry->next);
if (!last)
{
mysql_mutex_unlock(&log_requests.mutex); return;
}
/* Detach the head of the list that corresponds to persisted log writes. */ if (!entry)
log_requests.end= entry;
log_requests.start.store(entry, std::memory_order_relaxed);
mysql_mutex_unlock(&log_requests.mutex);
/* Now that we have released the mutex, notify the submitters
and free the head of the list. */ do
{
entry= pending;
pending= pending->next;
commit_checkpoint_notify_ha(entry->cookie);
my_free(entry);
} while (entry != last);
}
/** Invoke commit_checkpoint_notify_ha() to notify that outstanding
log writes have been completed. */ void log_flush_notify(lsn_t flush_lsn)
{ if (auto pending= log_requests.start.load(std::memory_order_acquire))
{
mysql_mutex_lock(&log_requests.mutex);
pending= log_requests.start.load(std::memory_order_relaxed);
log_flush_notify_and_unlock(pending, flush_lsn);
}
}
/** Handle a commit checkpoint request from server layer. Weputtherequestinaqueue,sothatwecannotifyupperlayerabout checkpointcompletewhenwehaveflushedtheredolog.
If we have already flushed all relevant redo log, we notify immediately.*/ staticvoid innodb_log_flush_request(void *cookie) noexcept
{
log_sys.latch.wr_lock();
lsn_t flush_lsn= log_sys.get_flushed_lsn(); /* Load lsn relaxed after flush_lsn was loaded from the same cache line */ const lsn_t lsn= log_sys.get_lsn();
log_sys.latch.wr_unlock();
if (flush_lsn >= lsn) /* All log is already persistent. */; elseif (UNIV_UNLIKELY(srv_force_recovery >= SRV_FORCE_NO_BACKGROUND)) /* Normally, srv_master_callback() should periodically invoke srv_sync_log_buffer_in_background(),whichshouldinitiatealog flushaboutonceeverysrv_flush_log_at_timeoutseconds.But, startingwiththeinnodb_force_recovery=2level,thatbackground
task will not run. */
log_write_up_to(flush_lsn= lsn, true); elseif (log_flush_request *req= static_cast<log_flush_request*>
(my_malloc(PSI_INSTRUMENT_ME, sizeof *req, MYF(MY_WME))))
{
req->next= nullptr;
req->cookie= cookie;
req->lsn= lsn;
log_flush_request *start= nullptr;
mysql_mutex_lock(&log_requests.mutex); /* In order to prevent a race condition where log_flush_notify() wouldskipanotificationdueto,wemustupdatelog_requests.startfrom
nullptr (empty) to the first req using std::memory_order_release. */ if (log_requests.start.compare_exchange_strong(start, req,
std::memory_order_release,
std::memory_order_relaxed))
{
ut_ad(!log_requests.end);
start= req; /* In case log_flush_notify() executed log_requests.start.load(std::memory_order_acquire)rightbefore oursuccessfulcompare_exchange,wemustre-readflush_lsnto
ensure that our request will be notified immediately if applicable. */
flush_lsn= log_sys.get_flushed_lsn();
} else
{ /* Append the entry to the list. Because we determined req->lsn before acquiringthemutex,thislistmaynotbeorderedbyreq->lsn,
even though log_flush_notify_and_unlock() assumes so. */
log_requests.end->next= req;
}
log_requests.end= req;
/* This hopefully addresses the hang that was reported in MDEV-24302. Uponreceivinganewrequest,wewillnotifyoldrequestsof
completion. */
log_flush_notify_and_unlock(start, flush_lsn); return;
} else
sql_print_error("Failed to allocate %zu bytes." " Commit checkpoint will be skipped.", sizeof *req);
/* This hopefully addresses the hang that was reported in MDEV-24302. Uponreceivinganewrequesttonotifyoflogwritesbecoming persistent,wewillnotifyoldrequestsofcompletion.Note: log_flush_notify()mayskipsomenotificationsbecauseitis
basically assuming that the list is in ascending order of LSN. */
log_flush_notify(flush_lsn);
commit_checkpoint_notify_ha(cookie);
}
/*****************************************************************//**
Rolls back a transaction to a savepoint.
@return0if success, HA_ERR_NO_SAVEPOINT if no savepoint with the
given name */ static int
innobase_rollback_to_savepoint( /*===========================*/
THD* thd, /*!< in: handle to the MySQL thread oftheuserwhosetransactionshould
be rolled back to savepoint */ void* savepoint) /*!< in: savepoint data */
{
DBUG_ENTER("innobase_rollback_to_savepoint");
trx_t *trx= check_trx_exists(thd);
/* We are reading trx->state without holding trx->mutex here, becausethesavepointrollbackshouldbeinvokedforarunning
active transaction that is associated with the current thread. */
ut_ad(trx->mysql_thd);
if (UNIV_UNLIKELY(*savept > trx->undo_no)) /* row_mysql_handle_errors() should have invoked rollback during
a bulk insert into an empty table. */
DBUG_RETURN(HA_ERR_NO_SAVEPOINT);
dberr_t error= trx->rollback(savept); /* Store the position for rolling back the next SQL statement */ if (trx->fts_trx)
{
fts_savepoint_laststmt_refresh(trx);
fts_savepoint_rollback(trx, savept);
}
trx->last_stmt_start= trx->undo_no;
trx->end_bulk_insert();
DBUG_RETURN(convert_error_code_to_mysql(error, 0, nullptr));
}
/*****************************************************************//**
Check whether innodb state allows to safely release MDL locks after
rollback to savepoint.
When binlog is on, MDL locks acquired after savepoint unit are not
released if there are any locks held in InnoDB.
@returntrueif it is safe, falseif its not safe. */ static bool
innobase_rollback_to_savepoint_can_release_mdl( /*===========================================*/
THD* thd) /*!< in: handle to the MySQL thread oftheuserwhosetransactionshould
be rolled back to savepoint */
{
DBUG_ENTER("innobase_rollback_to_savepoint_can_release_mdl");
trx_t* trx = check_trx_exists(thd);
/* If transaction has not acquired any locks then it is safe
to release MDL after rollback to savepoint */ if (UT_LIST_GET_LEN(trx->lock.trx_locks) == 0) {
DBUG_RETURN(true);
}
DBUG_RETURN(false);
}
/** Cancel any pending lock request associated with the current THD.
@sa THD::awake() @sa ha_kill_query() */ staticvoid innobase_kill_query(handlerton*, THD *thd, enum thd_kill_levels)
{
DBUG_ENTER("innobase_kill_query");
if (!lock) /* The transaction is not waiting for any lock. */; #ifdef WITH_WSREP elseif (trx->is_wsrep() && wsrep_thd_is_aborting(thd)) /* if victim has been signaled by BF thread and/or aborting is already progressing,followingqueryabortingisnotnecessaryanymore.
Also, BF thread should own trx mutex for the victim. */; #endif/* WITH_WSREP */ else
{ if (!trx->dict_operation)
{ /* Dictionary transactions must be immune to KILL, because they maybeexecutedaspartofamulti-transactionDDLoperation,such
as rollback_inplace_alter_table() or ha_innobase::delete_table(). */;
trx->error_state= DB_INTERRUPTED;
lock_sys.cancel<false>(trx, lock);
}
lock_sys.deadlock_check();
}
mysql_mutex_unlock(&lock_sys.wait_mutex);
}
switch (dict_tf_get_rec_format(flags)) { case REC_FORMAT_REDUNDANT: return(ROW_TYPE_REDUNDANT); case REC_FORMAT_COMPACT: return(ROW_TYPE_COMPACT); case REC_FORMAT_COMPRESSED: return(ROW_TYPE_COMPRESSED); case REC_FORMAT_DYNAMIC: return(ROW_TYPE_DYNAMIC);
}
}
ut_ad(0); return(ROW_TYPE_NOT_USED);
}
/****************************************************************//**
Get the table flags to use for the statement.
@return table flags */
/* enforce primary key when a table is created, but not when
an existing (hlindex?) table is auto-discovered */ if (srv_force_primary_key &&
thd_sql_command(thd) == SQLCOM_CREATE_TABLE) {
flags|= HA_REQUIRE_PRIMARY_KEY;
}
/* Need to use tx_isolation here since table flags is (also)
called before prebuilt is inited. */
if (thd_tx_isolation(thd) <= ISO_READ_COMMITTED) { return(flags | HA_CHECK_UNIQUE_AFTER_WRITE);
}
return(flags | HA_BINLOG_STMT_CAPABLE);
}
/****************************************************************//**
Returns the table type (storage engine name).
@return table type */
/****************************************************************//**
Returns the operations supported for indexes.
@return flags of supported operations */
/* For spatial index, we don't support descending scan
and ICP so far. */ if (table_share->key_info[key].algorithm == HA_KEY_ALG_RTREE) { return HA_READ_NEXT | HA_READ_ORDER| HA_READ_RANGE
| HA_KEYREAD_ONLY | HA_KEY_SCAN_NOT_ROR;
}
/****************************************************************//**
Returns the maximum key length.
@return maximum supported key length, in bytes */
uint
ha_innobase::max_supported_key_length() const /*=========================================*/
{ /* An InnoDB page must store >= 2 keys; a secondary key record mustalsocontaintheprimarykeyvalue.Therefore,ifboth theprimarykeyandthesecondarykeyareatthismaximumlength, itmustbelessthan1/4thofthefreespaceonapageincluding recordoverhead.
Note:Handle16kand32kpagesthesameheresincethelimits
are higher than imposed by MySQL. */
switch (srv_page_size) { case4096: /* Hack: allow mysql.innodb_index_stats to be created. */ /* FIXME: rewrite this API, and in sql_table.cc consider thatinindex-organizedtables(suchasInnoDB),secondary indexrecordswillbepaddedwiththePRIMARYKEY,instead
of some short ROWID or record heap address. */ return(1173); case8192: return(1536); default: return(3500);
}
}
/****************************************************************//**
Returns the key map of keys that are usable for scanning.
@return key_map_full */
/****************************************************************//**
Ensure that indexed virtual columns will be computed.
Needs to be done for indexes that are being added with inplace ALTER
in a different thread, because from the server point of view these
columns are not yet indexed.
Also needed if the primary key is being updated. For update statement: Mark all indexed virtual column
@param mark_for_update whether to mark indexed virtual columns for UPDATE operations
*/ void ha_innobase::column_bitmaps_signal(bool mark_for_update)
{ if (!table->vfield || table->current_lock != F_WRLCK) return;
if (UNIV_UNLIKELY(dict_index_is_online_ddl(
dict_table_get_first_index(m_prebuilt->table))))
row_log_mark_virtual_cols(m_prebuilt->table, table); elseif (mark_for_update)
{ for (uint j = 0, num_v= 0; j < table->s->virtual_fields; j++)
{
Field *vf= table->vfield[j]; if (!vf->stored_in_db() &&
m_prebuilt->table->v_cols[num_v++].m_col.ord_part)
table->mark_virtual_column_with_deps(vf);
}
}
}
/****************************************************************//**
Determines if table caching is supported.
@return HA_CACHE_TBL_ASKTRANSACT */
/** Normalizes a table name string. Anormalizednameconsistsofthedatabasenamecatenatedto'/' andtablename.Forexample:test/mytable. @paramnorm_nameNormalizedname,null-terminated. @paramnorm_name_sizesizeofthenorm_namebuffer
@param name Name to normalize */
size_t normalize_table_name(char *norm_name, size_t norm_name_size, constchar *name) noexcept
{ constchar* name_ptr;
ulint name_len; constchar* db_ptr;
ulint db_len; constchar* 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;
name_len = strlen(name_ptr);
/* skip any number of path separators */ while (ptr >= name && (*ptr == '\\' || *ptr == '/')) {
ptr--;
}
DBUG_ASSERT(ptr >= name);
/* seek to the last but one path separator or one char before
the beginning of name */
db_len = 0; while (ptr >= name && *ptr != '\\' && *ptr != '/') {
ptr--;
db_len++;
}
/** Match index columns between MySQL and InnoDB. Thisfunctioncheckswhethertheindexcolumninformation isconsistentbetweenKEYinfofrommysqlandthatfrominnodbindex. @param[in]key_infoIndexinfofrommysql @param[in]index_infoIndexinfofromInnoDB
@return true if all column types match. */ static bool
innobase_match_index_columns( const KEY* key_info, const dict_index_t* index_info)
{ const KEY_PART_INFO* key_part; const KEY_PART_INFO* key_end; const dict_field_t* innodb_idx_fld; const dict_field_t* innodb_idx_fld_end;
DBUG_ENTER("innobase_match_index_columns");
/* Check whether user defined index column count matches */ if (key_info->user_defined_key_parts !=
index_info->n_user_defined_cols) {
DBUG_RETURN(FALSE);
}
/* Check each index column's datatype. We do not check columnnamebecausethereexistscasethatindex columnnamegotmodifiedinmysqlbutsuchchangedoesnot propagatetoInnoDB. Onehiddenassumptionhereisthattheindexcolumnsequences
are matched up between those in mysql and InnoDB. */ for (; key_part != key_end; ++key_part) { unsigned is_unsigned; auto mtype = innodb_idx_fld->col->mtype;
/* Need to translate to InnoDB column type before
comparison. */ auto col_type = get_innobase_type_from_mysql_type(
&is_unsigned, key_part->field);
/* Ignore InnoDB specific system columns. */ while (mtype == DATA_SYS) {
innodb_idx_fld++;
if (innodb_idx_fld >= innodb_idx_fld_end) {
DBUG_RETURN(FALSE);
}
}
if (innodb_idx_fld->descending
!= !!(key_part->key_part_flag & HA_REVERSE_SORT)) {
DBUG_RETURN(FALSE);
}
if (col_type != mtype) { /* If the col_type we get from mysql type is a geometry datatype,weshouldcheckifmtypeisalegacytype from5.6,eitherupgradedtoDATA_GEOMETRYornot. Thisisindeednotanaccuratecheck,butshouldbe safe,sinceDATA_BLOBwouldbeupgradedoncewecreate spatialindexonitandweintendtouseDATA_GEOMETRY
for legacy GIS data types which are of var-length. */ switch (col_type) { case DATA_GEOMETRY: if (mtype == DATA_BLOB) { break;
} /* Fall through */ default: /* Column type mismatches */
DBUG_RETURN(false);
}
}
innodb_idx_fld++;
}
DBUG_RETURN(TRUE);
}
/** Build a template for a base column for a virtual column @param[in]tableMySQLTABLE @param[in]clust_indexInnoDBclusteredindex @param[in]fieldfieldinMySQLtable @param[in]colInnoDBcolumn @param[in,out]templtemplatetofill @param[in]col_nofieldindexforvirtualcol
*/ static void
innobase_vcol_build_templ( const TABLE* table,
dict_index_t* clust_index,
Field* field, const dict_col_t* col,
mysql_row_templ_t* templ,
ulint col_no)
{
templ->col_no = col_no;
templ->is_virtual = col->is_virtual();
/* Mark those columns could be base columns */ for (ulint i = 0; i < ib_table->n_v_cols; i++) { const dict_v_col_t* vcol = dict_table_get_nth_v_col(
ib_table, i);
/** Check consistency between .frm indexes and InnoDB indexes. @param[in]ib_tableInnoDBtabledefinition
@retval true if not errors were found */ bool
ha_innobase::check_index_consistency(const dict_table_t* ib_table) noexcept
{
ulint mysql_num_index = table->s->keys;
ulint ib_num_index = UT_LIST_GET_LEN(ib_table->indexes); bool ret = true;
ulint last_unique = 0;
/* If there exists inconsistency between MySQL and InnoDB dictionary (metadata)information,thenumberofindexdefinedinMySQL
could exceed that in InnoDB, return error */ if (ib_num_index < mysql_num_index) {
ret = false; goto func_exit;
}
/* For each index in the mysql key_info array, fetch its correspondingInnoDBindexpointerintoindex_mapping
array. */ for (ulint count = 0; count < mysql_num_index; count++) { const dict_index_t* index = dict_table_get_index_on_name(
ib_table, table->key_info[count].name.str);
if (index == NULL) {
sql_print_error("Cannot find index %s in InnoDB" " index dictionary.",
table->key_info[count].name.str);
ret = false; goto func_exit;
}
/* Double check fetched index has the same
column info as those in mysql key_info. */ if (!innobase_match_index_columns(&table->key_info[count],
index)) {
sql_print_error("Found index %s whose column info" " does not match that of MariaDB.",
table->key_info[count].name.str);
ret = false; goto func_exit;
}
if (index->is_unique()) {
ulint i = 0; while ((index = UT_LIST_GET_PREV(indexes, index))) i++; /* Check if any unique index in InnoDB dictionaryarere-orderedcomparedto
the index in .frm */ if (last_unique > i) {
m_int_table_flags
|= HA_DUPLICATE_KEY_NOT_IN_ORDER;
}
last_unique = i;
}
}
func_exit: return ret;
}
/** Get the maximum integer value of a numeric column. @paramfieldcolumndefinition
@return maximum allowed integer value */
ulonglong innobase_get_int_col_max_value(const Field *field)
{
ulonglong max_value = 0;
switch (field->key_type()) { /* TINY */ case HA_KEYTYPE_BINARY:
max_value = 0xFFULL; break; case HA_KEYTYPE_INT8:
max_value = 0x7FULL; break; /* SHORT */ case HA_KEYTYPE_USHORT_INT:
max_value = 0xFFFFULL; break; case HA_KEYTYPE_SHORT_INT:
max_value = 0x7FFFULL; break; /* MEDIUM */ case HA_KEYTYPE_UINT24:
max_value = 0xFFFFFFULL; break; case HA_KEYTYPE_INT24:
max_value = 0x7FFFFFULL; break; /* LONG */ case HA_KEYTYPE_ULONG_INT:
max_value = 0xFFFFFFFFULL; break; case HA_KEYTYPE_LONG_INT:
max_value = 0x7FFFFFFFULL; break; /* BIG */ case HA_KEYTYPE_ULONGLONG:
max_value = 0xFFFFFFFFFFFFFFFFULL; break; case HA_KEYTYPE_LONGLONG:
max_value = 0x7FFFFFFFFFFFFFFFULL; break; case HA_KEYTYPE_FLOAT: /* We use the maximum as per IEEE754-2008 standard, 2^24 */
max_value = 0x1000000ULL; break; case HA_KEYTYPE_DOUBLE: /* We use the maximum as per IEEE754-2008 standard, 2^53 */
max_value = 0x20000000000000ULL; break; default:
ut_error;
}
return(max_value);
}
/** Initialize the AUTO_INCREMENT column metadata.
/** Open an InnoDB table @param[in]nametablename @returnerrorcode
@retval 0 on success */ int
ha_innobase::open(constchar* name, int, uint)
{ char norm_name[FN_REFLEN];
if (UNIV_LIKELY(ib_table != nullptr)) {
} elseif (thd_ddl_options(thd)->import_tablespace()) { /* If the table does not exist and we are trying to import,createa"stub"tablesimilartotheeffects ofCREATETABLEfollowedbyALTERTABLE...DISCARD
TABLESPACE. */
HA_CREATE_INFO create_info; if (int err = prepare_create_stub_for_import(thd, norm_name,
create_info))
DBUG_RETURN(err);
create_info.option_struct= option_struct;
create(norm_name, table, &create_info, true, nullptr);
DEBUG_SYNC(thd, "ib_after_create_stub_for_import");
ib_table = open_dict_table(name, norm_name, is_part,
DICT_ERR_IGNORE_FK_NOKEY);
} else { if (is_part) {
sql_print_error("Failed to open table %s.\n",
norm_name);
}
set_my_errno(ENOENT);
/* Mark this table as corrupted, so the drop table
or force recovery can still use it, but not others. */
ib_table->file_unreadable = true;
ib_table->corrupted = true;
ib_table->release();
set_my_errno(ENOENT);
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
}
if ((ib_table->flags2 & DICT_TF2_DISCARDED)) { /* Allow an open because a proper DISCARD should have set alltheflagsandindexrootpagenumberstoFIL_NULLthat shouldpreventanyDMLfromrunningbutitshouldallowDDL
operations. */
} elseif (!ib_table->is_readable()) { const fil_space_t* space = ib_table->space; if (!space) {
ib_senderrf(
thd, IB_LOG_LEVEL_WARN,
ER_TABLESPACE_MISSING, norm_name);
}
if (!thd->tablespace_op) {
set_my_errno(ENOENT); int ret_err = HA_ERR_TABLESPACE_MISSING;
if (space && space->crypt_data
&& space->crypt_data->is_encrypted()) {
push_warning_printf(
thd,
Sql_condition::WARN_LEVEL_WARN,
HA_ERR_DECRYPTION_FAILED, "Table %s in file %s is encrypted" " but encryption service or" " used key_id %u is not available. " " Can't continue reading table.",
table_share->table_name.str,
space->chain.start->name,
space->crypt_data->key_id);
ret_err = HA_ERR_DECRYPTION_FAILED;
}
if (!check_index_consistency(ib_table)) {
sql_print_error("InnoDB indexes are inconsistent with what " "defined in .frm for table %s",
name);
}
/* Allocate a buffer for a 'row reference'. A row reference is astringofbytesoflengthref_lengthwhichuniquelyspecifies arowinourtable.NotethatMySQLmayalsocomparetworow referencesforequalitybydoingasimplememcmponthestrings
of length ref_length! */ if (!(m_prebuilt->clust_index_was_generated
= dict_index_is_auto_gen_clust(ib_table->indexes.start))) { if (m_primary_key >= MAX_KEY) {
ib_table->dict_frm_mismatch = DICT_FRM_NO_PK;
/* This mismatch could cause further problems ifnotattended,bringthistotheuser'sattention byprintingawarninginadditiontologamessage
in the errorlog */
ib_push_frm_error(thd, ib_table, table, 0, true);
/* If m_primary_key >= MAX_KEY, its (m_primary_key) valuecouldbeoutofboundifcontinuetoindex intokey_info[]array.FindInnoDBprimaryindex, andassignitskey_lengthtoref_length. Inaddition,sinceMySQLindexesaresortedstarting withprimaryindex,uniqueindexetc.,initialize ref_lengthtothefirstindexkeylengthin casewefailtofindInnoDBclusterindex.
/* Find corresponding cluster index
key length in MySQL's key_info[] array */ for (uint i = 0; i < table->s->keys; i++) {
dict_index_t* index;
index = innobase_get_index(i); if (dict_index_is_clust(index)) {
ref_length =
table->key_info[i].key_length;
}
}
} else { /* MySQL allocates the buffer for ref. key_info->key_lengthincludesspaceforallkey columns+onebyteforeachcolumnthatmaybe NULL.ref_lengthmustbeasexactaspossibleto savespace,becauseallrowreferencebuffersare
allocated based on ref_length. */
/* This mismatch could cause further problems ifnotattended,bringthistotheuserattention byprintingawarninginadditiontologamessage
in the errorlog */
ib_push_frm_error(thd, ib_table, table, 0, true);
}
ref_length = DATA_ROW_ID_LEN;
/* If we automatically created the clustered index, then MySQLdoesnotknowaboutit,andMySQLmustNOTbeaware oftheindexusedonscan,tomakeitavoidcheckingifwe updatethecolumnoftheindex.Thatiswhyweassertbelow thatkey_used_on_scanistheundefinedvalueMAX_KEY. Thecolumnistherowidintheautomaticalgenerationcase,
and it will never be updated anyway. */
if (key_used_on_scan != MAX_KEY) {
sql_print_warning( "Table %s key_used_on_scan is %u even " "though there is no primary key inside " "InnoDB.", name, key_used_on_scan);
}
}
/* Index block size in InnoDB: used by MySQL in query optimization */
stats.block_size = static_cast<uint>(srv_page_size);
/* Set plugin parser for fulltext index */ for (uint i = 0; i < table->s->keys; i++) { if (table->key_info[i].flags & HA_USES_PARSER) {
dict_index_t *index = innobase_get_index(i);
plugin_ref parser = table->key_info[i].parser;
/** Convert MySQL column number to dict_table_t::cols[] offset. @param[in]fieldnon-virtualcolumn
@return column number relative to dict_table_t::cols[] */ unsigned
innodb_col_no(const Field* field)
{
ut_ad(!innobase_is_s_fld(field)); const TABLE* table = field->table; unsigned col_no = 0;
ut_ad(field == table->field[field->field_index]); for (unsigned i = 0; i < field->field_index; i++) { if (table->field[i]->stored_in_db()) {
col_no++;
}
} return(col_no);
}
/** Opens dictionary table object using table name. For partition, we need to tryalternativelower/uppercasenamestosupportmovingdatafilesacross platforms. @param[in]table_namenameofthetable/partition @param[in]norm_namenormalizednameofthetable/partition @param[in]is_partitionifthisisapartitionofatable @param[in]ignore_errerrortoignoreforloadingdictionaryobject
@return dictionary table object or NULL if not found */
dict_table_t*
ha_innobase::open_dict_table( constchar* #ifdef _WIN32
table_name #endif
, constchar* norm_name, bool is_partition,
dict_err_ignore_t ignore_err)
{
DBUG_ENTER("ha_innobase::open_dict_table"); /* FIXME: try_drop_aborted */
dict_table_t* ib_table = dict_table_open_on_name(norm_name, false,
ignore_err);
if (NULL == ib_table && is_partition) { /* MySQL partition engine hard codes the file name separatoras"#P#".Thetextcaseisfixedevenif lower_case_table_namesissetto1or2.Thisistrue forsub-partitionnamesaswell.InnoDBalways normalisesfilenamestolowercaseonWindows,this canpotentiallycauseproblemswhencopying/moving tablesbetweenplatforms.
2)Ifwebootaninstallationfromothercase sensitiveplatforminWindows,wemightneedto checktheexistenceoftablenamewithoutlower
case in the system table. */ if (lower_case_table_names == 1) { char par_case_name[FN_REFLEN];
/* Check for the table using lower casename,includingthepartition
separator "P" */
system_charset_info->casedn_z(
norm_name, strlen(norm_name),
par_case_name, sizeof(par_case_name)); /* FIXME: try_drop_aborted */
ib_table = dict_table_open_on_name(
par_case_name, false, ignore_err);
} #ifdef _WIN32 /* Omitthewarningbelow,weknowweusedtoconvert #P#tolowercaseonWindows(wedonotdothatanymore). Mostlylikely,wehaveapartitionedtablefroman olderMariaDBversion.
*/ #else if (ib_table != NULL) {
sql_print_warning("Partition table %s opened" " after converting to lower" " case. The table may have" " been moved from a case" " in-sensitive file system." " Please recreate table in" " the current file system\n",
norm_name);
} #endif
}
uint
ha_innobase::max_supported_key_part_length() const /*==============================================*/
{ /* A table format specific index column length check will be performed
at ha_innobase::add_index() and row_create_index_for_mysql() */ return(REC_VERSION_56_MAX_INDEX_COL_LEN);
}
/******************************************************************//**
Closes a handle to an InnoDB table.
@return0 */
int
ha_innobase::close() /*================*/
{
DBUG_ENTER("ha_innobase::close");
/* The following accessor functions should really be inside MySQL code! */
#ifdef WITH_WSREP
size_t
wsrep_normalize_string( int mysql_type, /* in: MySQL type */
uint charset_number, /* in: number of the charset */ constunsignedchar* str, /* in: input data field */ unsignedchar* out_str, /* out: normalized string */
size_t str_length, /* in: data field length,
not UNIV_SQL_NULL */
size_t buf_length) /* in: total str buffer length */
case MYSQL_TYPE_BIT: case MYSQL_TYPE_STRING: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_VARCHAR:
{
CHARSET_INFO* charset;
/* Use the charset number to pick the right charset struct for thecomparison.SincetheMySQLfunctionget_charsetmaybe slowbeforeBarremovesthemutexoperationthere,wefirst
look at 2 common charsets directly. */
break;
} case MYSQL_TYPE_DECIMAL : case MYSQL_TYPE_TINY : case MYSQL_TYPE_SHORT : case MYSQL_TYPE_LONG : case MYSQL_TYPE_FLOAT : case MYSQL_TYPE_DOUBLE : case MYSQL_TYPE_NULL : case MYSQL_TYPE_TIMESTAMP : case MYSQL_TYPE_LONGLONG : case MYSQL_TYPE_INT24 : case MYSQL_TYPE_DATE : case MYSQL_TYPE_TIME : case MYSQL_TYPE_DATETIME : case MYSQL_TYPE_YEAR : case MYSQL_TYPE_NEWDATE : case MYSQL_TYPE_NEWDECIMAL : case MYSQL_TYPE_ENUM : case MYSQL_TYPE_SET : case MYSQL_TYPE_GEOMETRY : break; default: break;
}
return ret_length;
} #endif/* WITH_WSREP */
/******************************************************************//**
compare two character string according to their charset. */ int
innobase_fts_text_cmp( /*==================*/ constvoid* cs, /*!< in: Character set */ constvoid* p1, /*!< in: key */ constvoid* p2) /*!< in: node */
{ const CHARSET_INFO* charset = (const CHARSET_INFO*) cs; const fts_string_t* s1 = (const fts_string_t*) p1; const fts_string_t* s2 = (const fts_string_t*) p2;
/*************************************************************//**
Get the next token from the given string and store it in *token.
It is mostly copied from MyISAM's doc parsing function ft_simple_get_word()
@return length of string processed */
ulint
innobase_mysql_fts_get_token( /*=========================*/
CHARSET_INFO* cs, /*!< in: Character set */ const byte* start, /*!< in: start of text */ const byte* end, /*!< in: one character past end of
text */
fts_string_t* token) /*!< out: token's text */
{ int mbl; const uchar* doc = start;
/** Converts a MySQL type to an InnoDB type. Note that this function returns the'mtype'ofInnoDB.InnoDBdifferentiatesbetweenMySQL'sold<=4.1 VARCHARandthenewtrueVARCHARin>=5.0.3bythe'prtype'. @param[out]unsigned_flagDATA_UNSIGNEDifan'unsignedtype';atleast ENUMandSET,andunsignedintegertypesare'unsignedtypes' @param[in]fMySQLField
@return DATA_BINARY, DATA_VARCHAR, ... */
uint8_t
get_innobase_type_from_mysql_type(unsigned *unsigned_flag, const Field *field)
{ /* The following asserts try to check that the MySQL type code fits in
8 bits: this is used when DATA_NOT_NULL is ORed to the type */
if (field->real_type() == MYSQL_TYPE_ENUM
|| field->real_type() == MYSQL_TYPE_SET) {
/* MySQL has field->type() a string type for these, but the dataisactuallyinternallystoredasanunsignedinteger
code! */
*unsigned_flag = DATA_UNSIGNED; /* MySQL has its own unsigned flagsettozero,eventhough internallythisisanunsigned
integer type */ return(DATA_INT);
}
switch (field->type()) { /* NOTE that we only allow string types in DATA_MYSQL and
DATA_VARMYSQL */ case MYSQL_TYPE_VAR_STRING: /* old <= 4.1 VARCHAR */ case MYSQL_TYPE_VARCHAR: /* new >= 5.0.3 true VARCHAR */ if (field->binary()) { return(DATA_BINARY);
} elseif (field->charset() == &my_charset_latin1) { return(DATA_VARCHAR);
} else { return(DATA_VARMYSQL);
} case MYSQL_TYPE_BIT: case MYSQL_TYPE_STRING: if (field->binary() || field->key_type() == HA_KEYTYPE_BINARY) { return(DATA_FIXBINARY);
} elseif (field->charset() == &my_charset_latin1) { return(DATA_CHAR);
} else { return(DATA_MYSQL);
} case MYSQL_TYPE_NEWDECIMAL: return(DATA_FIXBINARY); case MYSQL_TYPE_LONG: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_INT24: case MYSQL_TYPE_DATE: case MYSQL_TYPE_YEAR: case MYSQL_TYPE_NEWDATE: return(DATA_INT); case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_TIMESTAMP: if (field->key_type() == HA_KEYTYPE_BINARY) { return(DATA_FIXBINARY);
} else { return(DATA_INT);
} case MYSQL_TYPE_FLOAT: return(DATA_FLOAT); case MYSQL_TYPE_DOUBLE: return(DATA_DOUBLE); case MYSQL_TYPE_DECIMAL: return(DATA_DECIMAL); case MYSQL_TYPE_GEOMETRY: return(DATA_GEOMETRY); case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_LONG_BLOB: return(DATA_BLOB); case MYSQL_TYPE_NULL: /* MySQL currently accepts "NULL" datatype, but will rejectsuchdatatypeinthenextrelease.Wewillcope
with it and not trigger assertion failure in 5.1 */ break; default:
ut_error;
}
return(0);
}
/*******************************************************************//**
Reads an unsigned integer value < 64k from 2 bytes, in the little-endian
storage format.
@return value */ staticinline
uint
innobase_read_from_2_little_endian( /*===============================*/ const uchar* buf) /*!< in: from where to read */
{ return((uint) ((ulint)(buf[0]) + 256 * ((ulint)(buf[1]))));
}
#ifdef WITH_WSREP /*******************************************************************//**
Stores a key value for a row to a buffer.
@return key value length as stored in buff */ static
uint16_t
wsrep_store_key_val_for_row( /*=========================*/
THD* thd,
TABLE* table,
uint keynr, /*!< in: key number */ char* buff, /*!< in/out: buffer for the key value (in MySQL
format) */
uint buff_len,/*!< in: buffer length */ const uchar* record, bool* key_is_null)/*!< out: full key was null */
{
KEY* key_info = table->key_info + keynr;
KEY_PART_INFO* key_part = key_info->key_part;
KEY_PART_INFO* end = key_part + key_info->user_defined_key_parts; char* buff_start = buff;
ulint buff_space = buff_len;
/* For multi byte character sets we need to calculate
the true length of the key */
if (len > 0 && cs->mbmaxlen > 1) { int error;
true_len= my_well_formed_length(cs,
(constchar *) data,
(constchar *) data + len,
(key_len / cs->mbmaxlen),
&error);
}
/* In a column prefix index, we may need to truncate
the stored value: */ if (true_len > key_len) {
true_len = key_len;
} /* cannot exceed max column length either, we may need to truncate
the stored value: */ if (true_len >= sizeof(normalized)) {
true_len = sizeof(normalized) - 1;
}
/* Normalize string if is not empty string */ if (true_len) {
ut_ad(data);
true_len= wsrep_normalize_string(
mysql_type, cs->number, data,
normalized, true_len,
REC_VERSION_56_MAX_INDEX_COL_LEN);
}
if (wsrep_protocol_version > 1) { /* Note that we always reserve the maximum possible lengthofthetrueVARCHARinthekeyvalue,though onlylenfirstbytesafterthe2lengthbytescontain actualdata.Therestofthespacewasresettozero
in the bzero() call above. */ if (true_len > buff_space) {
WSREP_DEBUG ( "write set key truncated for: %s\n",
wsrep_thd_query(thd));
true_len = buff_space;
}
memcpy(buff, normalized, true_len);
buff += true_len;
buff_space -= true_len;
} else {
buff += key_len;
}
} elseif (mysql_type == MYSQL_TYPE_TINY_BLOB
|| mysql_type == MYSQL_TYPE_MEDIUM_BLOB
|| mysql_type == MYSQL_TYPE_BLOB
|| mysql_type == MYSQL_TYPE_LONG_BLOB /* MYSQL_TYPE_GEOMETRY data is treated
as BLOB data in innodb. */
|| mysql_type == MYSQL_TYPE_GEOMETRY) {
/* All indexes on BLOB and TEXT are column prefix indexes,andwemayneedtotruncatethedatatobe
stored in the key value: */
if (true_len > key_len) {
true_len = key_len;
}
/* Normalize string if it is not empty string. Whentrue_len==0blob_dataisnotalways
nullptr it may be empty string i.e. "" */ if (true_len) {
ut_ad(blob_data);
true_len= wsrep_normalize_string(
mysql_type, cs->number, blob_data,
normalized, true_len,
REC_VERSION_56_MAX_INDEX_COL_LEN);
}
/* Note that we always reserve the maximum possible
length of the BLOB prefix in the key value. */ if (wsrep_protocol_version > 1) { if (true_len > buff_space) {
fprintf (stderr, "WSREP: key truncated: %s\n",
wsrep_thd_query(thd));
true_len = buff_space;
}
memcpy(buff, normalized, true_len);
buff += true_len;
buff_space -= true_len;
} else {
buff += key_len;
}
} else { /* Here we handle all other data types except the trueVARCHAR,BLOBandTEXT.Notethatthecolumn valuewestoremaybealsoinacolumnprefix
index. */
/* Character set for the field is defined only tofieldswhosetypeisstringandrealfield typeisnotenumorset.Forthesefieldscheck
if character set is multi byte. */
/* Normalize string if it is not empty string */ if (true_len) {
ut_ad(src_start);
true_len= wsrep_normalize_string(
mysql_type, cs->number,
src_start, normalized, true_len,
REC_VERSION_56_MAX_INDEX_COL_LEN);
} else {
ut_ad(src_start == nullptr);
}
if (true_len > buff_space) {
fprintf (stderr, "WSREP: key truncated: %s\n",
wsrep_thd_query(thd));
true_len = buff_space;
}
memcpy(buff, normalized, true_len);
} else { /* Copy only if there is data */ if (true_len) {
ut_ad(src_start);
memcpy(buff, src_start, true_len);
} else {
ut_ad(src_start == nullptr);
}
}
buff += true_len;
buff_space -= true_len;
}
}
ut_a(buff <= buff_start + buff_len);
DBUG_RETURN(static_cast<uint16_t>(buff - buff_start));
} #endif/* WITH_WSREP */ /**************************************************************//**
Determines if a field is needed in a m_prebuilt struct'template'.
@return field to use, or NULL if the field is not needed */ static const Field*
build_template_needs_field( /*=======================*/ bool index_contains, /*!< in: dict_index_t::contains_col_or_prefix(
i) */ bool read_just_key, /*!< in: TRUE when MySQL calls ha_innobase::extrawiththe argumentHA_EXTRA_KEYREAD;itisenough toreadjustcolumnsdefinedin theindex(i.e.,noreadofthe
clustered index record necessary) */ bool fetch_all_in_key, /*!< in: true=fetch all fields in
the index */ bool fetch_primary_key_cols, /*!< in: true=fetch the
primary key columns */
dict_index_t* index, /*!< in: InnoDB index to use */ const TABLE* table, /*!< in: MySQL table object */
ulint i, /*!< in: field index in InnoDB table */
ulint num_v) /*!< in: num virtual column so far */
{ const Field* field = table->field[i];
if (!field->stored_in_db()
&& ha_innobase::omits_virtual_cols(*table->s)) { return NULL;
}
if (!index_contains) { if (read_just_key) { /* If this is a 'key read', we do not need
columns that are not in the key */
return(NULL);
}
} elseif (fetch_all_in_key) { /* This field is needed in the query */
return(field);
}
if (bitmap_is_set(table->read_set, static_cast<uint>(i))
|| bitmap_is_set(table->write_set, static_cast<uint>(i))) { /* This field is needed in the query */
return(field);
}
ut_ad(i >= num_v); if (fetch_primary_key_cols
&& dict_table_col_in_clustered_key(index->table, i - num_v)) { /* This field is needed in the query */ return(field);
}
/* This field is not needed in the query, skip it */
return(NULL);
}
/**************************************************************//**
Determines if a field is needed in a m_prebuilt struct'template'.
@return whether the field is needed for index condition pushdown */ inline bool
build_template_needs_field_in_icp( /*==============================*/ const dict_index_t* index, /*!< in: InnoDB index */ const row_prebuilt_t* prebuilt,/*!< in: row fetch template */ bool contains,/*!< in: whether the index contains
column i */
ulint i, /*!< in: column number */ bool is_virtual) /*!< in: a virtual column or not */
{
ut_ad(contains == index->contains_col_or_prefix(i, is_virtual));
/**************************************************************//**
Adds a field to a m_prebuilt struct'template'.
@return the field template */ static
mysql_row_templ_t*
build_template_field( /*=================*/
row_prebuilt_t* prebuilt, /*!< in/out: template */
dict_index_t* clust_index, /*!< in: InnoDB clustered index */
dict_index_t* index, /*!< in: InnoDB index to use */
TABLE* table, /*!< in: MySQL table object */ const Field* field, /*!< in: field in MySQL table */
ulint i, /*!< in: field index in InnoDB table */
ulint v_no) /*!< in: field index for virtual col */
{
mysql_row_templ_t* templ; const dict_col_t* col;
if (!templ->is_virtual) {
templ->col_no = i;
col = dict_table_get_nth_col(index->table, i);
templ->clust_rec_field_no = dict_col_get_clust_pos(
col, clust_index); /* If clustered index record field is not found, lets print out
field names and all the rest to understand why field is not found. */ if (templ->clust_rec_field_no == ULINT_UNDEFINED) { constchar* tb_col_name = dict_table_get_col_name(clust_index->table, i).str;
dict_field_t* field=NULL;
size_t size = 0;
ib::fatal() << "Clustered record field for column " << i
<< " not found table n_user_defined "
<< clust_index->n_user_defined_cols
<< " index n_user_defined "
<< clust_index->table->n_cols - DATA_N_SYS_COLS
<< " InnoDB table "
<< clust_index->table->name
<< " field name "
<< (field ? field->name() : "NULL")
<< " MySQL table "
<< table->s->table_name.str
<< " field name "
<< (tb_col_name ? tb_col_name : "NULL")
<< " n_fields "
<< table->s->stored_fields
<< " query "
<< innobase_get_stmt_unsafe(current_thd, &size);
}
if (dict_index_is_clust(index)) {
templ->rec_field_no = templ->clust_rec_field_no;
} else { /* If we're in a secondary index, keep track *oftheoriginalindexpositionevenifthis *isjustaprefixindex;wewillusethis *latertoavoidaclusterindexlookupin
* some cases.*/
templ->rec_field_no = dict_index_get_nth_col_pos(index, i,
&templ->rec_prefix_field_no);
}
} else {
DBUG_ASSERT(!ha_innobase::omits_virtual_cols(*table->s));
col = &dict_table_get_nth_v_col(index->table, v_no)->m_col;
templ->clust_rec_field_no = v_no;
if (DATA_LARGE_MTYPE(templ->type)) {
prebuilt->templ_contains_blob = TRUE;
}
return(templ);
}
/**************************************************************//**
Builds a 'template' to the m_prebuilt struct. The template is used in fast
retrieval of just those column values MySQL needs in its processing. */
if (m_prebuilt->select_lock_type == LOCK_X || m_prebuilt->table->no_rollback()) { /* We always retrieve the whole clustered index record if we useexclusiverowlevellocks,forexample,ifthereadis doneinanUPDATEstatementorifweareusinganorollback
table */
/* We know we must at least fetch all columns in the
key, or all columns in the table */
if (m_prebuilt->read_just_key) { /* MySQL has instructed us that it is enough tofetchthecolumnsinthekey;lookslike MySQLcansetthisflagalsowhenthereis onlyaprefixofthecolumninthekey:in thatcaseweretrievethewholecolumnfrom
the clustered index */
fetch_all_in_key = TRUE;
} else {
whole_row = true;
}
} elseif (m_prebuilt->hint_need_to_fetch_extra_cols
== ROW_RETRIEVE_PRIMARY_KEY) { /* We must at least fetch all primary key cols. Note thatiftheclusteredindexwasinternallygenerated byInnoDBontherowid(noprimarykeywas defined),thenrow_search_mvcc()willalways retrievetherowidtoaspecialbufferinthe
m_prebuilt struct. */
if (m_prebuilt->in_fts_query) { /* Do clustered index lookup to fetch the FTS_DOC_ID */
m_prebuilt->need_to_access_clustered = true;
}
/* Either m_prebuilt->index should be a secondary index, or it
should be the clustered index. */
ut_ad(dict_index_is_clust(index) == (index == clust_index));
/* Below we check column by column if we need to access
the clustered index. */
/* Note that in InnoDB, i is the column number in the table.
MySQL calls columns 'fields'. */
ulint num_v = 0;
/* MDEV-31154: For pushed down index condition we don't support virtual columnandidx_cond_push()doescheckforit.ForrowIDfilteringwe don'tneedsuchrestrictionsbutwegetintotroubletryingtousethe ICPpath.
m_prebuilt->idx_cond = pushed_down ? this : nullptr;
if (m_prebuilt->idx_cond || m_prebuilt->pk_filter) { /* Push down an index condition, end_range check or row ID
filter */ for (ulint i = 0; i < n_fields; i++) { const Field* field = table->field[i]; constbool is_v = !field->stored_in_db();
if (is_v) { if (index_contains) { /* We want to ensure that ICP is not
used with virtual columns. */
ut_ad(!pushed_down);
m_prebuilt->idx_cond = nullptr;
}
num_v++; continue;
}
/* Test if an end_range or an index condition referstothefield.Notethat"index"and "index_contains"mayrefertotheclusteredindex. Indexconditionpushdownisrelativeto m_prebuilt->index(theindexthatisbeing
looked up first). */
/* When join_read_always_key() invokes this codeviahandler::ha_index_init()and ha_innobase::index_init(),end_rangeisnot yetinitialized.Becauseofthat,wemust alwayscheckforindex_contains,insteadof thesubset field->part_of_key.is_set(active_index)
which would be acceptable if end_range==NULL. */ if (build_template_needs_field_in_icp(
index, m_prebuilt, index_contains,
i - num_v, false)) { if (!whole_row) {
field = build_template_needs_field(
index_contains,
m_prebuilt->read_just_key,
fetch_all_in_key,
fetch_primary_key_cols,
index, table, i, num_v); if (!field) { continue;
}
}
if (index == m_prebuilt->index) {
templ->icp_rec_field_no
= templ->rec_field_no;
} else {
templ->icp_rec_field_no
= dict_index_get_nth_col_pos(
m_prebuilt->index,
i - num_v,
&templ->rec_prefix_field_no);
}
if (dict_index_is_clust(m_prebuilt->index)) {
ut_ad(templ->icp_rec_field_no
!= ULINT_UNDEFINED); /* If the primary key includes acolumnprefix,useitin indexconditionpushdown, becausetheconditionis evaluatedbeforefetchingany off-page(externallystored)
columns. */ if (templ->icp_rec_field_no
< m_prebuilt->index->n_uniq) { /* This is a key column;
all set. */ continue;
}
} elseif (templ->icp_rec_field_no
!= ULINT_UNDEFINED) { continue;
}
/* This is a column prefix index. Thecolumnprefixcanbeusedin
an end_range comparison. */
/* Index condition pushdown can be used on allcolumnsofasecondaryindex,andon thePRIMARYKEYcolumns.Ontheclustered index,itmustneverbeusedonotherthan PRIMARYKEYcolumns,becausethosecolumns maybestoredoff-page,andwewillnot fetchexternallystoredcolumnsbefore
checking the index condition. */ /* TODO: test the above with an assertion likethis.Notethatindexconditionsare currentlypusheddownaspartofthe "optimizerphase"whileend_rangeisdone aspartoftheexecutionphase.Therefore, wewereunabletouseanaccuratecondition forend_rangeinthe"if"conditionabove, andthefollowingassertionwouldfail. ut_ad(!dict_index_is_clust(m_prebuilt->index) ||templ->rec_field_no <m_prebuilt->index->n_uniq);
*/
}
}
num_v = 0;
ut_ad(m_prebuilt->idx_cond_n_cols == m_prebuilt->n_template); if (m_prebuilt->idx_cond_n_cols == 0) { /* No columns to push down. It is safe to jump to np ICP
path. */
m_prebuilt->idx_cond = nullptr; goto no_icp;
}
/* Include the fields that are not needed in index condition
pushdown. */ for (ulint i = 0; i < n_fields; i++) { const Field* field = table->field[i]; constbool is_v = !field->stored_in_db(); if (is_v && skip_virtual) {
num_v++; continue;
}
if (index != clust_index && m_prebuilt->need_to_access_clustered) { /* Change rec_field_no's to correspond to the clustered index
record */ for (ulint i = 0; i < m_prebuilt->n_template; i++) {
mysql_row_templ_t* templ
= &m_prebuilt->mysql_template[i];
/********************************************************************//** This special handling is really to overcome the limitations of MySQL's
binlogging. We need to eliminate the non-determinism that will arise in
INSERT ... SELECT type of statements, since MySQL binlog only stores the
min value of the autoinc interval. Once that is fixed we can get rid of
the special lock handling.
@return DB_SUCCESS if all OK else error code */
switch (innobase_autoinc_lock_mode) { case AUTOINC_NO_LOCKING: /* Acquire only the AUTOINC mutex. */
m_prebuilt->table->autoinc_mutex.wr_lock(); break;
case AUTOINC_NEW_STYLE_LOCKING: /* For simple (single/multi) row INSERTs/REPLACEs and RBR events,wefallbacktotheoldstyleonlyifanother transactionhasalreadyacquiredtheAUTOINClockon behalfofaLOADFILEorINSERT...SELECTetc.typeof
statement. */ switch (thd_sql_command(m_user_thd)) { case SQLCOM_INSERT: case SQLCOM_REPLACE: case SQLCOM_END: // RBR event /* Acquire the AUTOINC mutex. */
m_prebuilt->table->autoinc_mutex.wr_lock(); /* We need to check that another transaction isn't
already holding the AUTOINC lock on the table. */ if (!m_prebuilt->table->n_waiting_or_granted_auto_inc_locks) { /* Do not fall back to old style locking. */
DBUG_RETURN(error);
}
m_prebuilt->table->autoinc_mutex.wr_unlock(); break; default: break;
} /* Use old style locking. */ /* fall through */ case AUTOINC_OLD_STYLE_LOCKING:
DBUG_EXECUTE_IF("die_if_autoinc_old_lock_style_used",
ut_ad(0););
error = row_lock_table_autoinc_for_mysql(m_prebuilt);
if (error == DB_SUCCESS) {
/* Acquire the AUTOINC mutex. */
m_prebuilt->table->autoinc_mutex.wr_lock();
} break;
default:
ut_error;
}
DBUG_RETURN(error);
}
/********************************************************************//**
Store the autoinc value in the table. The autoinc value is only set if
it's greater than the existing autoinc value in the table.
@return DB_SUCCESS if all went well else error code */
dberr_t
ha_innobase::innobase_set_max_autoinc( /*==================================*/
ulonglong auto_inc) /*!< in: value to store */
{
dberr_t error;
switch (UNIV_EXPECT(m_prebuilt->trx->state, TRX_STATE_ACTIVE)) { default:
ut_ad("invalid state" == 0); /* fall through */ case TRX_STATE_ABORTED: break; case TRX_STATE_NOT_STARTED:
m_prebuilt->trx->will_lock= true; /* fall through */ case TRX_STATE_ACTIVE: if (altering_to_supported ||
!DICT_TF_GET_ZIP_SSIZE(m_prebuilt->table->flags) ||
!innodb_read_only_compressed) return0;
/********************************************************************//**
Stores a row in an InnoDB database, to the table specified in this
handle.
@return error code */
int
ha_innobase::write_row( /*===================*/ const uchar* record) /*!< in: a row in MySQL format */
{
dberr_t error; #ifdef WITH_WSREP bool wsrep_auto_inc_inserted= false; #endif int error_result = 0; bool auto_inc_used = false;
DBUG_ENTER("ha_innobase::write_row");
trx_t* trx = thd_to_trx(m_user_thd);
mariadb_set_stats temp(trx, handler_stats);
/* Validation checks before we commence write_row operation. */ if (int err = is_valid_trx()) {
DBUG_RETURN(err);
}
ins_mode_t vers_set_fields; /* Handling of Auto-Increment Columns. */ if (table->next_number_field && record == table->record[0]) {
/* Reset the error code before calling
innobase_get_auto_increment(). */
m_prebuilt->autoinc_error = DB_SUCCESS;
if ((error_result = update_auto_increment())) { /* MySQL errors are passed straight back. */ goto func_exit;
}
auto_inc_used = true;
}
/* Prepare INSERT graph that will be executed for actual INSERT
(This is a one time operation) */ if (m_prebuilt->mysql_template == NULL
|| m_prebuilt->template_type != ROW_MYSQL_WHOLE_ROW) {
/* Build the template used in converting quickly between
the two database formats */
/* Execute insert graph that will result in actual insert. */
error = row_insert_for_mysql((byte*) record, m_prebuilt, vers_set_fields);
DEBUG_SYNC(m_user_thd, "ib_after_row_insert");
/* Handling of errors related to auto-increment. */ if (auto_inc_used) {
ulonglong auto_inc;
/* Note the number of rows processed for this statement, used byget_auto_increment()todeterminethenumberofAUTO-INC valuestoreserve.Thisisonlyusefulforamult-valueINSERT
and is a statement level counter. */ if (trx->n_autoinc_rows > 0) {
--trx->n_autoinc_rows;
}
/* Get the value that MySQL attempted to store in the table.*/
auto_inc = table->next_number_field->val_uint();
switch (error) { case DB_DUPLICATE_KEY:
/* A REPLACE command and LOAD DATA INFILE REPLACE handleaduplicatekeyerrorthemselves,butwe mustupdatetheautoinccounterifweareperforming
those statements. */
switch (thd_sql_command(m_user_thd)) { case SQLCOM_LOAD: if (!trx->duplicates) { break;
}
case SQLCOM_REPLACE: case SQLCOM_INSERT_SELECT: case SQLCOM_REPLACE_SELECT: goto set_max_autoinc;
#ifdef WITH_WSREP /* workaround for LP bug #355000, retrying the insert */ case SQLCOM_INSERT:
WSREP_DEBUG("DUPKEY error for autoinc\n" "THD %llu, value %llu, off %llu inc %llu",
m_user_thd->thread_id,
auto_inc,
m_prebuilt->autoinc_offset,
m_prebuilt->autoinc_increment);
if (wsrep_auto_inc_inserted &&
wsrep_thd_retry_counter(m_user_thd) == 0 &&
!thd_test_options(m_user_thd,
OPTION_NOT_AUTOCOMMIT |
OPTION_BEGIN)) {
WSREP_DEBUG( "retrying insert: %s",
wsrep_thd_query(m_user_thd));
error= DB_SUCCESS;
wsrep_thd_self_abort(m_user_thd); /* jump straight to func exit over
* later wsrep hooks */ goto func_exit;
} break; #endif/* WITH_WSREP */
default: break;
}
break;
case DB_SUCCESS: /* If the actual value inserted is greater than theupperlimitoftheinterval,thenwetryand updatethetableupperlimit.Note:last_value
will be 0 if get_auto_increment() was not called. */
if (auto_inc >= m_prebuilt->autoinc_last_value) {
set_max_autoinc: /* We need the upper limit of the col type to check for
whether we update the table autoinc counter or not. */
ulonglong col_max_value =
table->next_number_field->get_max_int_value();
/* This should filter out the negative
values set explicitly by the user. */ if (auto_inc <= col_max_value) {
ut_ad(m_prebuilt->autoinc_increment > 0);
/* We use o_ptr and n_ptr to dig up the actual data for
comparison. */
field_mysql_type = field->type();
col_type = col->mtype;
switch (col_type) {
case DATA_BLOB: case DATA_GEOMETRY:
o_ptr = row_mysql_read_blob_ref(&o_len, o_ptr, o_len);
n_ptr = row_mysql_read_blob_ref(&n_len, n_ptr, n_len);
break;
case DATA_VARCHAR: case DATA_BINARY: case DATA_VARMYSQL: if (field_mysql_type == MYSQL_TYPE_VARCHAR) { /* This is a >= 5.0.3 type true VARCHAR where therealpayloaddatalengthisstoredin
1 or 2 bytes */
/* Let us use a dummy dfield to make the conversion
from the MySQL column format to the InnoDB format */
/* If the length of new geometry object is 0, means thisobjectisinvalidgeometryobject,weneed
to block it. */ if (DATA_GEOMETRY_MTYPE(col_type)
&& o_len != 0 && n_len == 0) {
trx->error_info = clust_index; return(DB_CANT_CREATE_GEOMETRY_OBJECT);
}
if (n_len != UNIV_SQL_NULL) {
dict_col_copy_type(
col, dfield_get_type(&dfield));
/* If the update changes a column with an FTS index on it, we thenaddanupdatecolumnnodewithanewdocumentidtothe otherchanges.WepiggybackourchangesonthenormalUPDATE
to reduce processing and IO overhead. */ if (!prebuilt->table->fts) {
trx->fts_next_doc_id = 0;
} elseif (changes_fts_column || changes_fts_doc_col) {
dict_table_t* innodb_table = prebuilt->table;
ufield = uvect->fields + n_changed;
if (!DICT_TF2_FLAG_IS_SET(
innodb_table, DICT_TF2_FTS_HAS_DOC_ID)) {
/* If Doc ID is managed by user, and if any FTSindexedcolumnhasbeenupdated,itscorresponding DocIDmustalsobeupdated.Otherwise,return
error */ if (changes_fts_column && !changes_fts_doc_col) {
ib::warn() << "A new Doc ID must be supplied" " while updating FTS indexed columns."; return(DB_FTS_INVALID_DOCID);
}
/* Doc ID must monotonically increase */
ut_ad(innodb_table->fts->cache); if (doc_id < prebuilt->table->fts->cache->next_doc_id) {
ib::warn() << "FTS Doc ID must be larger than "
<< innodb_table->fts->cache->next_doc_id
- 1 << " for table "
<< innodb_table->name;
return(DB_FTS_INVALID_DOCID);
}
trx->fts_next_doc_id = doc_id;
} else { /* If the Doc ID is a hidden column, it can't be
changed by user */
ut_ad(!changes_fts_doc_col);
/* Doc ID column is hidden, a new Doc ID will be
generated by following fts_update_doc_id() call */
trx->fts_next_doc_id = 0;
}
++n_changed;
} else { /* We have a Doc ID column, but none of FTS indexed columnsaretouched,northeDocIDcolumn,soset fts_next_doc_idtoUINT64_UNDEFINED,whichmeansdonot
update the Doc ID column */
trx->fts_next_doc_id = UINT64_UNDEFINED;
}
if (const TABLE_LIST *tl= table->pos_in_table_list)
{ const uint8 op_map= tl->trg_event_map | tl->slave_fk_event_map; /* Used to avoid reading history in FK check on DELETE (see MDEV-16210). */
prebuilt->upd_node->is_delete =
(op_map & trg2bit(TRG_EVENT_DELETE)
&& table->versioned(VERS_TIMESTAMP))
? VERSIONED_DELETE : NO_DELETE;
}
if (prebuilt->versioned_write) { /* Guaranteed by CREATE TABLE, but anyway we make sure we
generate history only when there are versioned fields. */
DBUG_ASSERT(vers_fields);
prebuilt->upd_node->vers_make_update(trx);
}
ut_ad(uvect->validate()); return(DB_SUCCESS);
}
#ifdef WITH_WSREP static int
wsrep_calc_row_hash( /*================*/
byte* digest, /*!< in/out: md5 sum */ const uchar* row, /*!< in: row in MySQL format */
TABLE* table, /*!< in: table in MySQL data
dictionary */
row_prebuilt_t* prebuilt) /*!< in: InnoDB prebuilt struct */
{ void *ctx = alloca(my_md5_context_size());
my_md5_init(ctx);
for (uint i = 0; i < table->s->fields; i++) {
byte null_byte=0;
byte true_byte=1; unsigned is_unsigned;
const Field* field = table->field[i]; if (!field->stored_in_db()) { continue;
}
auto ptr = row + get_field_offset(table, field);
ulint len = field->pack_length();
case DATA_VARCHAR: case DATA_BINARY: case DATA_VARMYSQL: if (field->type() == MYSQL_TYPE_VARCHAR) { /* This is a >= 5.0.3 type true VARCHAR where therealpayloaddatalengthisstoredin
1 or 2 bytes */
if (int err = is_valid_trx()) {
DBUG_RETURN(err);
}
if (m_upd_buf == NULL) {
ut_ad(m_upd_buf_size == 0);
/* Create a buffer for packing the fields of a record. Why table->reclengthdidnotworkhere?Obviously,becausechar fieldswhenpackedactuallybecame1bytelonger,whenwealso
stored the string length as the first byte. */
if (!uvect->n_fields) { /* This is the same as success, but instructs MySQLthattherowisnotreallyupdatedandit shouldnotincreasethecountofupdatedrows.
This is fix for http://bugs.mysql.com/29157 */
DBUG_RETURN(HA_ERR_RECORD_IS_THE_SAME);
} else { if (m_prebuilt->upd_node->is_delete) {
trx->fts_next_doc_id = 0;
}
/* row_start was updated by vers_make_update()
in calc_row_difference() */
error = row_update_for_mysql(m_prebuilt);
if (error == DB_SUCCESS && m_prebuilt->versioned_write /* Multiple UPDATE of same rows in single transaction create
historical rows only once. */
&& trx->id != table->vers_start_id(new_row)) { /* UPDATE is not used by ALTER TABLE. Just precaution
as we don't need history generation for ALTER TABLE. */
ut_ad(thd_sql_command(m_user_thd) != SQLCOM_ALTER_TABLE);
error = row_insert_for_mysql((byte*) old_row,
m_prebuilt,
ROW_INS_HISTORICAL);
}
}
if (error == DB_SUCCESS && autoinc) { /* A value for an AUTO_INCREMENT column
was specified in the UPDATE statement. */
/* We need the upper limit of the col type to check for
whether we update the table autoinc counter or not. */
ulonglong col_max_value =
table->found_next_number_field->get_max_int_value();
/* This should filter out the negative
values set explicitly by the user. */ if (autoinc <= col_max_value) {
ulonglong offset;
ulonglong increment;
#ifdef WITH_WSREP if (!err && /* For sequences, InnoDB transaction may not have been started yet.
Check THD-level wsrep state in that case. */
(trx->is_wsrep()
|| (trx->state == TRX_STATE_NOT_STARTED && wsrep_on(m_user_thd)))
&& wsrep_thd_is_local(m_user_thd)
&& !wsrep_thd_ignore_table(m_user_thd)
&& (thd_sql_command(m_user_thd) != SQLCOM_CREATE_TABLE)
&& (thd_sql_command(m_user_thd) != SQLCOM_LOAD ||
thd_binlog_format(m_user_thd) == BINLOG_FORMAT_ROW)) {
/* We use table-level shared key for SEQUENCES
and normal key append for others. */ if (table->s->table_type == TABLE_TYPE_SEQUENCE) { if (wsrep_append_table_key(m_user_thd, *m_prebuilt->table, false))
DBUG_RETURN(HA_ERR_INTERNAL_ERROR);
} elseif (wsrep_append_keys(m_user_thd,
wsrep_protocol_version >= 4
? WSREP_SERVICE_KEY_UPDATE
: WSREP_SERVICE_KEY_EXCLUSIVE,
old_row, new_row)) {
WSREP_DEBUG("::update_rows::wsrep_append_keys failed THD %llu for %s.%s",
m_user_thd->thread_id,
table->s->db.str,
table->s->table_name.str);
DBUG_RETURN(HA_ERR_INTERNAL_ERROR);
}
} #endif/* WITH_WSREP */
DBUG_RETURN(err);
}
/**********************************************************************//**
Deletes a row given as the parameter.
@return error number or0 */
int
ha_innobase::delete_row( /*====================*/ const uchar* record) /*!< in: a row in MySQL format */
{
dberr_t error;
trx_t* trx = thd_to_trx(m_user_thd);
DBUG_ENTER("ha_innobase::delete_row");
if (int err = is_valid_trx()) {
DBUG_RETURN(err);
} if (!m_prebuilt->upd_node) {
row_get_prebuilt_update_vector(m_prebuilt);
}
/* This is a delete */
m_prebuilt->upd_node->is_delete = table->versioned_write(VERS_TRX_ID)
&& table->vers_end_field()->is_max(
table->vers_end_field()->ptr_in_record(record))
&& trx->id != table->vers_start_id(record)
? VERSIONED_DELETE
: PLAIN_DELETE;
trx->fts_next_doc_id = 0;
/**********************************************************************//**
Removes a new lock set on a row, if it was not read optimistically. This can
be called after a row has been read in the processing of an UPDATE or a DELETE
query. */
switch (m_prebuilt->row_read_type) { case ROW_READ_WITH_LOCKS: if (m_prebuilt->trx->isolation_level > TRX_ISO_READ_COMMITTED) break; /* fall through */ case ROW_READ_TRY_SEMI_CONSISTENT:
row_unlock_for_mysql(m_prebuilt, FALSE); break; case ROW_READ_DID_SEMI_CONSISTENT:
m_prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; break;
}
DBUG_VOID_RETURN;
}
/* See handler.h and row0mysql.h for docs on this function. */
/* See handler.h and row0mysql.h for docs on this function. */ void ha_innobase::try_semi_consistent_read(bool yes)
{
ut_ad(m_prebuilt->trx == thd_to_trx(ha_thd())); /* Row read type is set to semi consistent read if this was requestedbytheSQLlayerandthetransactionisolationlevelis
READ UNCOMMITTED or READ COMMITTED. */
m_prebuilt->row_read_type = yes
&& m_prebuilt->trx->isolation_level <= TRX_ISO_READ_COMMITTED
? ROW_READ_TRY_SEMI_CONSISTENT
: ROW_READ_WITH_LOCKS;
}
/******************************************************************//**
Initializes a handle to use an index.
@return0or error number */
int
ha_innobase::index_init( /*====================*/
uint keynr, /*!< in: key (index) number */ bool)
{
DBUG_ENTER("index_init");
DBUG_RETURN(change_active_index(keynr));
}
/******************************************************************//**
Currently does nothing.
@return0 */
int
ha_innobase::index_end(void) /*========================*/
{
DBUG_ENTER("index_end");
active_index = MAX_KEY;
in_range_check_pushed_down = FALSE;
m_ds_mrr.dsmrr_close();
DBUG_RETURN(0);
}
/** Convert a MariaDB search mode to an InnoDB search mode. @tparamlast_matchwhetherlast_match_modeistobeset @paramfind_flagMariaDBsearchmode @parammodeInnoDBsearchmode @paramlast_match_modepointertoha_innobase::m_last_match_mode
@return whether the search mode is unsupported */ template<bool last_match= false> staticbool convert_search_mode_to_innobase(ha_rkey_function find_flag,
page_cur_mode_t &mode,
uint *last_match_mode= nullptr)
{
mode= PAGE_CUR_LE; if (last_match)
*last_match_mode= 0;
switch (find_flag) { case HA_READ_KEY_EXACT: /* this does not require the index to be UNIQUE */ if (last_match)
*last_match_mode= ROW_SEL_EXACT; /* fall through */ case HA_READ_KEY_OR_NEXT:
mode= PAGE_CUR_GE; returnfalse; case HA_READ_AFTER_KEY:
mode= PAGE_CUR_G; returnfalse; case HA_READ_BEFORE_KEY:
mode= PAGE_CUR_L; returnfalse; case HA_READ_PREFIX_LAST: if (last_match)
*last_match_mode= ROW_SEL_EXACT_PREFIX; /* fall through */ case HA_READ_KEY_OR_PREV: case HA_READ_PREFIX_LAST_OR_PREV: returnfalse; case HA_READ_MBR_CONTAIN:
mode= PAGE_CUR_CONTAIN; returnfalse; case HA_READ_MBR_INTERSECT:
mode= PAGE_CUR_INTERSECT; returnfalse; case HA_READ_MBR_WITHIN:
mode= PAGE_CUR_WITHIN; returnfalse; case HA_READ_MBR_DISJOINT:
mode= PAGE_CUR_DISJOINT; returnfalse; case HA_READ_MBR_EQUAL:
mode= PAGE_CUR_MBR_EQUAL; returnfalse; case HA_READ_PREFIX: break;
}
B)IftheuserhasexplicitlysetMySQLtablelevellocks,thenMySQL doesNOTcall::external_lockatthestartofthestatement.Todetermine whenweareatthestartofanewSQLstatementweatthestartof ::index_readalsocomparethequeryidtothelatestqueryidwherethe tablehandleinstancewasused.Ifithaschanged,weknowweareatthe startofanewSQLstatement.Sincethequeryidcantheoretically overwrap,weusethistestonlyasasecondarywayofdeterminingthe
start of a new SQL statement. */
/**********************************************************************//**
Positions an index cursor to the index specified in the handle. Fetches the
row if any.
@return0, HA_ERR_KEY_NOT_FOUND, or error number */
int
ha_innobase::index_read( /*====================*/
uchar* buf, /*!< in/out: buffer for the returned
row */ const uchar* key_ptr, /*!< in: key value; if this is NULL wepositionthecursoratthe startorendofindex;thiscan alsocontainanInnoDBrowid,in whichcasekey_lenistheInnoDB rowidlength;thekeyvaluecan alsobeaprefixofafullkeyvalue, andthelastcolumncanbeaprefix
of a full column */
uint key_len,/*!< in: key value length */ enum ha_rkey_function find_flag)/*!< in: search flags from my_base.h */
{
DBUG_ENTER("index_read");
DEBUG_SYNC_C("ha_innobase_index_read_begin");
if (!m_prebuilt->index_usable) {
DBUG_RETURN(index->is_corrupted()
? HA_ERR_INDEX_CORRUPT
: HA_ERR_TABLE_DEF_CHANGED);
}
if (index->type & DICT_FTS) {
DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
}
const trx_state_t trx_state{m_prebuilt->trx->state}; if (trx_state == TRX_STATE_ABORTED) {
DBUG_RETURN(HA_ERR_ROLLBACK);
}
/* For R-Tree index, we will always place the page lock to
pages being searched */ if (index->is_spatial() && !m_prebuilt->trx->will_lock) { if (trx_state != TRX_STATE_NOT_STARTED) {
DBUG_RETURN(HA_ERR_READ_ONLY_TRANSACTION);
} else {
m_prebuilt->trx->will_lock = true;
}
}
/* Note that if the index for which the search template is built is not
necessarily m_prebuilt->index, but can also be the clustered index */
if (m_prebuilt->sql_stat_start) {
build_template(false);
}
if (key_len) {
ut_ad(key_ptr); /* Convert the search key value to InnoDB format into
m_prebuilt->search_tuple */
DBUG_ASSERT(m_prebuilt->search_tuple->n_fields > 0);
} else {
ut_ad(find_flag != HA_READ_KEY_EXACT); /* We position the cursor to the last or the first entry
in the index */
DBUG_EXECUTE_IF("ib_select_query_failure", ret = DB_ERROR;);
if (UNIV_LIKELY(ret == DB_SUCCESS)) {
table->status = 0;
DBUG_RETURN(0);
}
table->status = STATUS_NOT_FOUND;
switch (ret) { case DB_TABLESPACE_DELETED:
ib_senderrf(
m_user_thd, IB_LOG_LEVEL_ERROR,
ER_TABLESPACE_DISCARDED,
table->s->table_name.str);
DBUG_RETURN(HA_ERR_TABLESPACE_MISSING); case DB_RECORD_NOT_FOUND: case DB_END_OF_INDEX:
DBUG_RETURN(HA_ERR_KEY_NOT_FOUND); case DB_TABLESPACE_NOT_FOUND:
ib_senderrf(
m_user_thd, IB_LOG_LEVEL_ERROR,
ER_TABLESPACE_MISSING,
table->s->table_name.str);
DBUG_RETURN(HA_ERR_TABLESPACE_MISSING); default:
DBUG_RETURN(convert_error_code_to_mysql(
ret, m_prebuilt->table->flags,
m_user_thd));
}
}
/*******************************************************************//**
The following functions works like index_read, but it find the last
row with the current key value or prefix.
@return0, HA_ERR_KEY_NOT_FOUND, or an error code */
int
ha_innobase::index_read_last( /*=========================*/
uchar* buf, /*!< out: fetched row */ const uchar* key_ptr,/*!< in: key value, or a prefix of a full
key value */
uint key_len)/*!< in: length of the key val or prefix
in bytes */
{ return(index_read(buf, key_ptr, key_len, HA_READ_PREFIX_LAST));
}
/********************************************************************//**
Get the index for a handle. Does not change active index.
@return NULL or index instance. */
dict_index_t*
ha_innobase::innobase_get_index( /*============================*/
uint keynr) /*!< in: use this index; MAX_KEY means always clusteredindex,evenifitwasinternally
generated by InnoDB */
{
KEY* key = NULL;
dict_table_t* ib_table = m_prebuilt->table;
dict_index_t* index;
DBUG_ENTER("innobase_get_index");
if (keynr != MAX_KEY && table->s->keys > 0) {
key = &table->key_info[keynr];
index = dict_table_get_index_on_name(ib_table, key->name.str);
} else {
index = dict_table_get_first_index(ib_table);
}
if (index == NULL) {
sql_print_error( "InnoDB could not find key no %u with name %s" " from dict cache for table %s",
keynr, key ? key->name.str : "NULL",
ib_table->name.m_name);
}
DBUG_RETURN(index);
}
/********************************************************************//**
Changes the active index of a handle.
@return0or error code */
int
ha_innobase::change_active_index( /*=============================*/
uint keynr) /*!< in: use this index; MAX_KEY means always clustered index,evenifitwasinternallygeneratedby
InnoDB */
{
DBUG_ENTER("change_active_index");
if (m_prebuilt->index->is_primary()) {
ut_ad(m_prebuilt->index->table->corrupted);
push_warning_printf(
m_user_thd, Sql_condition::WARN_LEVEL_WARN,
ER_TABLE_CORRUPT, "InnoDB: Table %s is corrupted.",
table_name);
DBUG_RETURN(ER_TABLE_CORRUPT);
} else {
push_warning_printf(
m_user_thd, Sql_condition::WARN_LEVEL_WARN,
HA_ERR_INDEX_CORRUPT, "InnoDB: Index %s for table %s is" " marked as corrupted",
m_prebuilt->index->name(),
table_name);
DBUG_RETURN(HA_ERR_INDEX_CORRUPT);
}
} else {
push_warning_printf(
m_user_thd, Sql_condition::WARN_LEVEL_WARN,
HA_ERR_TABLE_DEF_CHANGED, "InnoDB: insufficient history for index %u",
keynr);
}
/* The caller seems to ignore this. Thus, we must check
this again in row_search_mvcc(). */
DBUG_RETURN(convert_error_code_to_mysql(DB_MISSING_HISTORY, 0, NULL));
}
ut_a(m_prebuilt->search_tuple != 0);
/* Initialization of search_tuple is not needed for FT index sinceFTsearchreturnsrankonly.Inadditionengineshould
be able to retrieve FTS_DOC_ID column value if necessary. */ if (m_prebuilt->index->type & DICT_FTS) { for (uint i = 0; i < table->s->fields; i++) { if (m_prebuilt->read_just_key
&& bitmap_is_set(table->read_set, i)
&& !strcmp(table->s->field[i]->field_name.str,
FTS_DOC_ID.str)) {
m_prebuilt->fts_doc_id_in_read_set = true; break;
}
}
} else {
ulint n_fields = dict_index_get_n_unique_in_tree(
m_prebuilt->index);
/* If it's FTS query and FTS_DOC_ID exists FTS_DOC_ID field is
always added to read_set. */
m_prebuilt->fts_doc_id_in_read_set = m_prebuilt->in_fts_query
&& m_prebuilt->read_just_key
&& m_prebuilt->index->contains_col_or_prefix(
m_prebuilt->table->fts->doc_col, false);
}
/* MySQL changes the active index for a handle also during some queries,forexampleSELECTMAX(a),SUM(a)firstretrievestheMAX() andthencalculatesthesum.Previouslyweplayedsafeandused theflagROW_MYSQL_WHOLE_ROWbelow,butthatcausedunnecessary
copying. Starting from MySQL-4.1 we use a more efficient flag here. */
build_template(false);
DBUG_RETURN(0);
}
/* @return true if it's necessary to switch current statement log format from STATEMENTtoROWifbinarylogformatisMIXEDandautoincrementvalues
are changed in the statement */ bool ha_innobase::autoinc_lock_mode_stmt_unsafe() const
{ return innobase_autoinc_lock_mode == AUTOINC_NO_LOCKING;
}
/***********************************************************************//**
Reads the next or previous row from a cursor, which must have previously been
positioned using index_read.
@return0, HA_ERR_END_OF_FILE, or error number */
int
ha_innobase::general_fetch( /*=======================*/
uchar* buf, /*!< in/out: buffer for next row in MySQL
format */
uint direction, /*!< in: ROW_SEL_NEXT or ROW_SEL_PREV */
uint match_mode) /*!< in: 0, ROW_SEL_EXACT, or
ROW_SEL_EXACT_PREFIX */
{
DBUG_ENTER("general_fetch");
trx_t* trx = m_prebuilt->trx;
ut_ad(trx == thd_to_trx(m_user_thd));
switch (UNIV_EXPECT(trx->state, TRX_STATE_ACTIVE)) { default:
ut_ad("invalid state" == 0); /* fall through */ case TRX_STATE_ABORTED:
DBUG_RETURN(HA_ERR_ROLLBACK); case TRX_STATE_ACTIVE: case TRX_STATE_NOT_STARTED: break;
}
/***********************************************************************//**
Reads the next row from a cursor, which must have previously been
positioned using index_read.
@return0, HA_ERR_END_OF_FILE, or error number */
int
ha_innobase::index_next( /*====================*/
uchar* buf) /*!< in/out: buffer for next row in MySQL
format */
{ return(general_fetch(buf, ROW_SEL_NEXT, 0));
}
/*******************************************************************//**
Reads the next row matching to the key value given as the parameter.
@return0, HA_ERR_END_OF_FILE, or error number */
int
ha_innobase::index_next_same( /*=========================*/
uchar* buf, /*!< in/out: buffer for the row */ const uchar*, uint)
{ return(general_fetch(buf, ROW_SEL_NEXT, m_last_match_mode));
}
/***********************************************************************//**
Reads the previous row from a cursor, which must have previously been
positioned using index_read.
@return0, HA_ERR_END_OF_FILE, or error number */
int
ha_innobase::index_prev( /*====================*/
uchar* buf) /*!< in/out: buffer for previous row in MySQL format */
{ return(general_fetch(buf, ROW_SEL_PREV, 0));
}
/********************************************************************//**
Positions a cursor on the first record in an index and reads the
corresponding row to buf.
@return0, HA_ERR_END_OF_FILE, or error code */
int
ha_innobase::index_first( /*=====================*/
uchar* buf) /*!< in/out: buffer for the row */
{
DBUG_ENTER("index_first");
int error = index_read(buf, NULL, 0, HA_READ_AFTER_KEY);
/* MySQL does not seem to allow this to return HA_ERR_KEY_NOT_FOUND */
if (error == HA_ERR_KEY_NOT_FOUND) {
error = HA_ERR_END_OF_FILE;
}
DBUG_RETURN(error);
}
/********************************************************************//**
Positions a cursor on the last record in an index and reads the
corresponding row to buf.
@return0, HA_ERR_END_OF_FILE, or error code */
int
ha_innobase::index_last( /*====================*/
uchar* buf) /*!< in/out: buffer for the row */
{
DBUG_ENTER("index_last");
int error = index_read(buf, NULL, 0, HA_READ_BEFORE_KEY);
/* MySQL does not seem to allow this to return HA_ERR_KEY_NOT_FOUND */
if (error == HA_ERR_KEY_NOT_FOUND) {
error = HA_ERR_END_OF_FILE;
}
DBUG_RETURN(error);
}
/****************************************************************//**
Initialize a table scan.
@return0or error number */
int
ha_innobase::rnd_init( /*==================*/ bool scan) /*!< in: true if table/index scan FALSE otherwise */
{ int err;
/* Don't use rowid filter when doing full table scan or rnd_pos calls.*/ if (!scan) {
m_disable_rowid_filter = true;
}
/* Store the active index value so that we can restore the original
value after a scan */
if (err && !scan) { /* Restore the original value in case of error */
m_disable_rowid_filter = false;
}
/* Don't use semi-consistent read in random row reads (by position).
This means we must disable semi_consistent_read if scan is false */
if (!scan) {
try_semi_consistent_read(0);
}
m_start_of_scan = true;
return(err);
}
/*****************************************************************//**
Ends a table scan.
@return0or error number */
int
ha_innobase::rnd_end(void) /*======================*/
{
m_disable_rowid_filter = false; return(index_end());
}
/*****************************************************************//**
Reads the next row in a table scan (also used to read the FIRST row
in a table scan).
@return0, HA_ERR_END_OF_FILE, or error number */
int
ha_innobase::rnd_next( /*==================*/
uchar* buf) /*!< in/out: returns the row in this buffer,
in MySQL format */
{ int error;
DBUG_ENTER("rnd_next");
if (m_start_of_scan) {
error = index_first(buf);
if (error == HA_ERR_KEY_NOT_FOUND) {
error = HA_ERR_END_OF_FILE;
}
/**********************************************************************//**
Fetches a row from the table based on a row reference.
@return0, HA_ERR_KEY_NOT_FOUND, or error code */
int
ha_innobase::rnd_pos( /*=================*/
uchar* buf, /*!< in/out: buffer for the row */
uchar* pos) /*!< in: primary key value of the row in the MySQLformat,ortherowidiftheclustered indexwasinternallygeneratedbyInnoDB;the
length of data in pos has to be ref_length */
{
DBUG_ENTER("rnd_pos");
DBUG_DUMP("key", pos, ref_length);
/* Note that we assume the length of the row reference is fixed
for the table, and it is == ref_length */
DBUG_ASSERT(m_disable_rowid_filter == true); int error = index_read(buf, pos, (uint)ref_length, HA_READ_KEY_EXACT);
/**********************************************************************//**
Initialize FT index scan
@return0or error number */
int ha_innobase::ft_init()
{
DBUG_ENTER("ft_init");
trx_t* trx = check_trx_exists(ha_thd());
/* FTS queries are not treated as autocommit non-locking selects. ThisisbecausetheFTSimplementationcanacquirelocksbehind thescenes.Thishasnotbeenverifiedbutitissafertotreat
them as regular read only transactions for now. */ switch (trx->state) { default:
DBUG_RETURN(HA_ERR_ROLLBACK); case TRX_STATE_ACTIVE: break; case TRX_STATE_NOT_STARTED:
trx->will_lock = true; break;
}
/* If there is an FTS scan in progress, stop it */
fts_result_t* result = (reinterpret_cast<NEW_FT_INFO*>(ft_handler))->ft_result; if (result)
result->current= NULL;
DBUG_RETURN(rnd_init(false));
}
/**********************************************************************//**
Initialize FT index scan
@return FT_INFO structure if successful or NULL */
/* Multi byte character sets like utf32 and utf16 are not compatiblewithsomestringfunctionused.Sotoconvertthem
to uft8 before we proceed. */ if (char_set->mbminlen != 1) {
buf_tmp_used = innobase_convert_string(
buf_tmp, sizeof(buf_tmp) - 1,
&my_charset_utf8mb3_general_ci,
query, query_len, (CHARSET_INFO*) char_set,
&num_errors);
/* FTS queries are not treated as autocommit non-locking selects. ThisisbecausetheFTSimplementationcanacquirelocksbehind thescenes.Thishasnotbeenverifiedbutitissafertotreat
them as regular read only transactions for now. */
/* Table does not have an FTS index */ if (!ft_table->fts || ib_vector_is_empty(ft_table->fts->indexes)) {
my_error(ER_TABLE_HAS_NO_FT, MYF(0)); return(NULL);
}
/* If tablespace is discarded, we should return here */ if (!ft_table->space) {
my_error(ER_TABLESPACE_MISSING, MYF(0), ft_table->name.m_name); return(NULL);
}
if (keynr == NO_SUCH_KEY) { /* FIXME: Investigate the NO_SUCH_KEY usage */
index = reinterpret_cast<dict_index_t*>
(ib_vector_getp(ft_table->fts->indexes, 0));
} else {
index = innobase_get_index(keynr);
}
/* FIXME: Re-evaluate the condition when Bug 14469540 is resolved */
m_prebuilt->in_fts_query = true;
return(reinterpret_cast<FT_INFO*>(fts_hdl));
}
/*****************************************************************//**
Set up search tuple for a query through FTS_DOC_ID_INDEX on
supplied Doc ID. This is used by MySQL to retrieve the documents
once the search result (Doc IDs) is available
@return DB_SUCCESS or DB_INDEX_CORRUPT
*/ static
dberr_t
innobase_fts_create_doc_id_key( /*===========================*/
dtuple_t* tuple, /* in/out: m_prebuilt->search_tuple */ const dict_index_t*
index, /* in: index (FTS_DOC_ID_INDEX) */
doc_id_t* doc_id) /* in/out: doc id to search, value couldbechangedtostorageformat
used for search. */
{
doc_id_t temp_doc_id;
dfield_t* dfield = dtuple_get_nth_field(tuple, 0); const ulint n_uniq = index->table->fts_n_uniq();
if (dict_index_get_n_unique(index) != n_uniq) return DB_INDEX_CORRUPT;
#ifdef UNIV_DEBUG /* The unique Doc ID field should be an eight-bytes integer */
dict_field_t* field = dict_index_get_nth_field(index, 0);
ut_a(field->col->mtype == DATA_INT);
ut_ad(sizeof(*doc_id) == field->fixed_len);
ut_ad(!strcmp(index->name, FTS_DOC_ID_INDEX.str)); #endif/* UNIV_DEBUG */
/* Convert to storage byte order */
mach_write_to_8(reinterpret_cast<byte*>(&temp_doc_id), *doc_id);
*doc_id = temp_doc_id;
dfield_set_data(dfield, doc_id, sizeof(*doc_id));
result = reinterpret_cast<NEW_FT_INFO*>(ft_handler)->ft_result;
if (result->current == NULL) { /* This is the case where the FTS query did not
contain and matching documents. */ if (result->rankings_by_id != NULL) { /* Now that we have the complete result, we needtosortthedocumentidsontheirrank
calculation. */
/* If we only need information from result we can return
without fetching the table row */ if (ft_prebuilt->read_just_key) {
table->status= 0; return(0);
}
dict_index_t* index;
index = m_prebuilt->table->fts_doc_id_index;
/* Must find the index */
ut_a(index != NULL);
/* Switch to the FTS doc id index */
m_prebuilt->index = index;
/* We pass a pointer of search_doc_id because it will be convertedtostoragebyteorderusedinthesearch
tuple. */
dberr_t ret = innobase_fts_create_doc_id_key(
tuple, index, &search_doc_id);
if (ret == DB_SUCCESS) {
mariadb_set_stats temp(m_prebuilt->trx, handler_stats);
ret = row_search_mvcc(
buf, PAGE_CUR_GE, m_prebuilt,
ROW_SEL_EXACT, 0);
}
int error;
switch (ret) { case DB_SUCCESS:
error = 0;
table->status = 0; break; case DB_RECORD_NOT_FOUND:
result->current = const_cast<ib_rbt_node_t*>(
rbt_next(result->rankings_by_rank,
result->current));
if (!result->current) { /* exhaust the result set, should return HA_ERR_END_OF_FILEjustlike ha_innobase::general_fetch()and/or
ha_innobase::index_first() etc. */
error = HA_ERR_END_OF_FILE;
table->status = STATUS_NOT_FOUND;
} else { goto next_record;
} break; case DB_END_OF_INDEX:
error = HA_ERR_END_OF_FILE;
table->status = STATUS_NOT_FOUND; break; case DB_TABLESPACE_DELETED:
int
ha_innobase::wsrep_append_keys( /*===========================*/
THD *thd,
Wsrep_service_key_type key_type, /*!< in: access type of this row operation:
(shared, exclusive, reference...) */ const uchar* record0, /* in: row in MySQL format */ const uchar* record1) /* in: row in MySQL format */
{ /* Sanity check: newly inserted records should always be passed with EXCLUSIVEkeytype,alltherestareexpectedtocarryapre-image
*/
ut_a(record1 != NULL || key_type == WSREP_SERVICE_KEY_EXCLUSIVE);
/*********************************************************************//**
Stores a reference to the current row to 'ref' field of the handle. Note
that in the case where we have generated the clustered index for the
table, the function parameter is illogical: we MUST ASSUME that 'record'
is the current 'position' of the handle, because if row ref is actually
the row id internally generated in InnoDB, then 'record' does not contain
it. We just guess that the row id must be for the record where the handle
was positioned the last time. */
void
ha_innobase::position( /*==================*/ const uchar* record) /*!< in: row in MySQL format */
{
uint len;
ut_a(m_prebuilt->trx == thd_to_trx(ha_thd()));
if (m_prebuilt->clust_index_was_generated) { /* No primary key was defined for the table and we generatedtheclusteredindexfromrowid:the rowreferencewillbetherowid,notanykeyvalue
that MySQL knows of */
len = DATA_ROW_ID_LEN;
memcpy(ref, m_prebuilt->row_id, len);
} else {
/* Copy primary key as the row reference */
KEY* key_info = table->key_info + m_primary_key;
key_copy(ref, (uchar*)record, key_info, key_info->key_length);
len = key_info->key_length;
}
ut_ad(len == ref_length);
}
/*****************************************************************//**
Check whether there exist a column named as "FTS_DOC_ID", which is
reserved for InnoDB FTS Doc ID
@returntrueif there exist a "FTS_DOC_ID" column */ static bool
create_table_check_doc_id_col( /*==========================*/
trx_t* trx, /*!< in: InnoDB transaction handle */ const TABLE* form, /*!< in: information on table
columns and indexes */
ulint* doc_id_col) /*!< out: Doc ID column number if thereexistaFTS_DOC_IDcolumn, ULINT_UNDEFINEDifcolumnisofthe
wrong type/name/size */
{ for (ulint i = 0; i < form->s->fields; i++) { const Field* field = form->field[i]; if (!field->stored_in_db()) { continue;
}
unsigned unsigned_type;
auto col_type = get_innobase_type_from_mysql_type(
&unsigned_type, field);
auto col_len = field->pack_length();
if (field->field_name.streq(FTS_DOC_ID)) {
/* Note the name is case sensitive due to
our internal query parser */ if (col_type == DATA_INT
&& !field->real_maybe_null()
&& col_len == sizeof(doc_id_t)
&& (strcmp(field->field_name.str,
FTS_DOC_ID.str) == 0)) {
*doc_id_col = i;
} else {
push_warning_printf(
trx->mysql_thd,
Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: FTS_DOC_ID column must be" " of BIGINT NOT NULL type, and named" " in all capitalized characters");
my_error(ER_WRONG_COLUMN_NAME, MYF(0),
field->field_name.str);
*doc_id_col = ULINT_UNDEFINED;
}
return(true);
}
}
return(false);
}
/** Finds all base columns needed to compute a given generated column. Thisisreturnedasabitmap,infield->table->tmp_set. Worksforbothdict_v_col_tanddict_s_col_tcolumns. @param[in]tableInnoDBtable @param[in]fieldMySQLfield
@param[in,out] col virtual or stored column */ template <typename T> void
prepare_vcol_for_base_setup( /*========================*/ const dict_table_t* table, const Field* field,
T* col)
{
ut_ad(col->num_base == 0);
ut_ad(col->base_col == NULL);
/** Set up base columns for stored column @param[in]tableInnoDBtable @param[in]fieldMySQLfield
@param[in,out] s_col stored column */ void
innodb_base_col_setup_for_stored( const dict_table_t* table, const Field* field,
dict_s_col_t* s_col)
{
ulint n = 0;
prepare_vcol_for_base_setup(table, field, s_col);
for (uint i= 0; i < field->table->s->fields; ++i) { const Field* base_field = field->table->field[i];
if (base_field->stored_in_db()
&& bitmap_is_set(&field->table->tmp_set, i)) {
ulint z; for (z = 0; z < table->n_cols; z++) { const Lex_cstring name =
dict_table_get_col_name(table, z); if (base_field->field_name.streq(name)) { break;
}
}
/* MySQL does the name length check. But we do additional check
on the name length here */ const size_t table_name_len = strlen(m_table_name); if (table_name_len > MAX_FULL_NAME_LEN) {
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_TABLE_NAME, "InnoDB: Table Name or Database Name is too long");
DBUG_RETURN(ER_TABLE_NAME);
}
if (m_table_name[table_name_len - 1] == '/') {
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_TABLE_NAME, "InnoDB: Table name is empty");
DBUG_RETURN(ER_WRONG_TABLE_NAME);
}
/* Find out the number of virtual columns. */
ulint num_v = 0; constbool omit_virtual = ha_innobase::omits_virtual_cols(*m_form->s); const ulint n_cols = omit_virtual
? m_form->s->stored_fields : m_form->s->fields;
if (!omit_virtual) { for (ulint i = 0; i < n_cols; i++) {
num_v += !m_form->field[i]->stored_in_db();
}
}
/* Check whether there already exists a FTS_DOC_ID column */ if (create_table_check_doc_id_col(m_trx, m_form, &doc_id_col)){
/* Raise error if the Doc ID column is of wrong type or name */ if (doc_id_col == ULINT_UNDEFINED) {
DBUG_RETURN(HA_ERR_GENERIC);
} else {
has_doc_id_col = TRUE;
}
}
/* Adjust the number of columns for the FTS hidden field */ const ulint actual_n_cols = n_cols
+ (m_flags2 & DICT_TF2_FTS && !has_doc_id_col);
if (charset_no > MAX_CHAR_COLL_NUM) { /* in data0type.h we assume that the
number fits in one byte in prtype */
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_CANT_CREATE_TABLE, "In InnoDB, charset-collation codes" " must be below 256." " Unsupported code " ULINTPF ".",
charset_no);
dict_mem_table_free(table);
DBUG_RETURN(ER_CANT_CREATE_TABLE);
}
}
auto col_len = field->pack_length();
/* The MySQL pack length contains 1 or 2 bytes length field foratrueVARCHAR.Letussubtractthat,sothattheInnoDB columnlengthintheInnoDBdatadictionaryisthereal
maximum byte length of the actual data. */
long_true_varchar = 0;
if (field->type() == MYSQL_TYPE_VARCHAR) {
col_len -= ((Field_varstring*) field)->length_bytes;
/* First check whether the column to be added has a
system reserved name. */ if (dict_col_name_is_reserved(field->field_name)){
my_error(ER_WRONG_COLUMN_NAME, MYF(0),
field->field_name.str); goto err_col;
}
if (num_v) { for (ulint i = 0, j = 0; i < n_cols; i++) {
dict_v_col_t* v_col;
const Field* field = m_form->field[i];
if (field->stored_in_db()) { continue;
}
v_col = dict_table_get_nth_v_col(table, j);
j++;
innodb_base_col_setup(table, field, v_col);
}
}
/** Fill base columns for the stored column present in the list. */ if (table->s_cols && !table->s_cols->empty()) { for (ulint i = 0; i < n_cols; i++) {
Field* field = m_form->field[i];
if (!innobase_is_s_fld(field)) { continue;
}
dict_s_col_list::iterator it; for (it = table->s_cols->begin();
it != table->s_cols->end(); ++it) {
dict_s_col_t s_col = *it;
/*****************************************************************//**
Creates an index in an InnoDB database. */ inline int
create_index( /*=========*/
trx_t* trx, /*!< in: InnoDB transaction handle */ const TABLE* form, /*!< in: information on table
columns and indexes */ const ha_table_option_struct& o,
dict_table_t* table, /*!< in,out: table */
uint key_num) /*!< in: index number */
{
dict_index_t* index; int error; const KEY* key;
ulint* field_lengths;
DBUG_ENTER("create_index");
key = form->key_info + key_num;
/* Assert that "GEN_CLUST_INDEX" cannot be used as non-primary index */
ut_a(!key->name.streq(GEN_CLUST_INDEX));
if (key->algorithm == HA_KEY_ALG_FULLTEXT ||
key->algorithm == HA_KEY_ALG_RTREE) { /* Only one of these can be specified at a time. */
ut_ad(!(key->flags & HA_NOSAME));
index = dict_mem_index_create(table, key->name.str,
key->algorithm == HA_KEY_ALG_RTREE
? DICT_SPATIAL : DICT_FTS,
key->user_defined_key_parts);
for (ulint i = 0; i < key->user_defined_key_parts; i++) { const Field* field = key->key_part[i].field;
/* We do not support special (Fulltext or Spatial)
index on virtual columns */ if (!field->stored_in_db()) {
ut_ad(0);
DBUG_RETURN(HA_ERR_UNSUPPORTED);
}
/* We pass 0 as the space id, and determine at a lower level the space
id where to store the table */
index = dict_mem_index_create(table, key->name.str,
ind_type, key->user_defined_key_parts);
for (ulint i = 0; i < key->user_defined_key_parts; i++) {
KEY_PART_INFO* key_part = key->key_part + i;
ulint prefix_len; unsigned is_unsigned;
/* (The flag HA_PART_KEY_SEG denotes in MySQL a columnprefixfieldinanindex:weonlystorea specifiednumberoffirstbytesofthecolumnto theindexfield.)Theflagdoesnotseemtobe properlysetbyMySQL.Letusfallbackontesting thelengthofthekeypartversusthecolumn. Wefirstreachtothetable'scolumn;iftheindexisona prefix,key_part->fieldisnotthetable'scolumn(it'sa "fake"fieldforgedinopen_table_from_share()withlength equaltothelengthoftheprefix);sowehavetogoto
form->field. */
Field* field= form->field[key_part->field->field_index]; if (field == NULL)
ut_error;
switch (col_type) { default:
prefix_len = key_part->length; break; case DATA_INT: case DATA_FLOAT: case DATA_DOUBLE: case DATA_DECIMAL:
sql_print_error( "MariaDB is trying to create a column" " prefix index field, on an" " inappropriate data type. Table" " name %s, column name %s.",
form->s->table_name.str,
key_part->field->field_name.str);
/* Even though we've defined max_supported_key_part_length, we stilldoourowncheckingusingfield_lengthstobeabsolutely
sure we don't create too long indexes. */
ulint flags = table->flags;
/** Return a display name for the row format @param[in]row_formatRowFormat
@return row format name */ static constchar*
get_row_format_name( enum row_type row_format)
{ switch (row_format) { case ROW_TYPE_COMPACT: return("COMPACT"); case ROW_TYPE_COMPRESSED: return("COMPRESSED"); case ROW_TYPE_DYNAMIC: return("DYNAMIC"); case ROW_TYPE_REDUNDANT: return("REDUNDANT"); case ROW_TYPE_DEFAULT: return("DEFAULT"); case ROW_TYPE_FIXED: return("FIXED"); case ROW_TYPE_PAGE: case ROW_TYPE_NOT_USED: break;
} return("NOT USED");
}
/** Validate DATA DIRECTORY option.
@return true if valid, false if not. */ bool
create_table_info_t::create_option_data_directory_is_valid()
{ bool is_valid = true;
/* Use DATA DIRECTORY only with file-per-table. */ if (!m_allow_file_per_table) {
push_warning(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: DATA DIRECTORY requires" " innodb_file_per_table.");
is_valid = false;
}
/* Do not use DATA DIRECTORY with TEMPORARY TABLE. */ if (m_create_info->tmp_table()) {
push_warning(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: DATA DIRECTORY cannot be used" " for TEMPORARY tables.");
is_valid = false;
}
/* We check for a DATA DIRECTORY mixed with TABLESPACE in
create_option_tablespace_is_valid(), no need to here. */
return(is_valid);
}
/** Validate the create options. Check that the options KEY_BLOCK_SIZE, ROW_FORMAT,DATADIRECTORY,TEMPORARYarecompatiblewith eachotherandothersettings.TheseCREATEOPTIONSarenotvalidated hereunlessinnodb_strict_modeison.Withstrictmode,thisfunction willreporteachproblemitfindsusingacustommessagewitherror codeER_ILLEGAL_HA_CREATE_OPTION,notitsbuilt-inmessage.
@return NULL if valid, string name of bad option if not. */ constchar*
create_table_info_t::create_options_are_invalid()
{ bool has_key_block_size = (m_create_info->key_block_size != 0);
/* If innodb_strict_mode is not set don't do any more validation. */ if (!THDVAR(m_thd, strict_mode)) { return(NULL);
}
/* Check if a non-zero KEY_BLOCK_SIZE was specified. */ if (has_key_block_size) { if (is_temp || innodb_read_only_compressed) {
my_error(ER_UNSUPPORTED_COMPRESSED_TABLE, MYF(0)); return("KEY_BLOCK_SIZE");
}
switch (m_create_info->key_block_size) {
ulong kbs_max; case1: case2: case4: case8: case16: /* The maximum KEY_BLOCK_SIZE (KBS) is UNIV_PAGE_SIZE_MAX.Butifsrv_page_sizeis smallerthanUNIV_PAGE_SIZE_MAX,themaximum
KBS is also smaller. */
kbs_max = ut_min( 1U << (UNIV_PAGE_SSIZE_MAX - 1), 1U << (PAGE_ZIP_SSIZE_MAX - 1)); if (m_create_info->key_block_size > kbs_max) {
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: KEY_BLOCK_SIZE=%lu" " cannot be larger than %lu.",
m_create_info->key_block_size,
kbs_max);
ret = "KEY_BLOCK_SIZE";
}
/* Check for a valid InnoDB ROW_FORMAT specifier and
other incompatibilities. */ switch (row_format) { case ROW_TYPE_COMPRESSED: if (is_temp || innodb_read_only_compressed) {
my_error(ER_UNSUPPORTED_COMPRESSED_TABLE, MYF(0)); return("ROW_FORMAT");
} if (!m_allow_file_per_table) {
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: ROW_FORMAT=%s requires" " innodb_file_per_table.",
get_row_format_name(row_format));
ret = "ROW_FORMAT";
} break; case ROW_TYPE_DYNAMIC: case ROW_TYPE_COMPACT: case ROW_TYPE_REDUNDANT: if (has_key_block_size) {
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: cannot specify ROW_FORMAT = %s" " with KEY_BLOCK_SIZE.",
get_row_format_name(row_format));
ret = "KEY_BLOCK_SIZE";
} break; case ROW_TYPE_DEFAULT: break; case ROW_TYPE_FIXED: case ROW_TYPE_PAGE: case ROW_TYPE_NOT_USED:
push_warning(
m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: invalid ROW_FORMAT specifier.");
ret = "ROW_TYPE"; break;
}
/* Don't support compressed table when page size > 16k. */ if ((has_key_block_size || row_format == ROW_TYPE_COMPRESSED)
&& srv_page_size > UNIV_PAGE_SIZE_DEF) {
push_warning(m_thd, Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: Cannot create a COMPRESSED table" " when innodb_page_size > 16k.");
if (has_key_block_size) {
ret = "KEY_BLOCK_SIZE";
} else {
ret = "ROW_TYPE";
}
}
return(ret);
}
/*****************************************************************//**
Check engine specific table options not handled by SQL-parser.
@return NULL if valid, string ifnot */ constchar*
create_table_info_t::check_table_options()
{ enum row_type row_format = m_create_info->row_type; const ha_table_option_struct *options= m_create_info->option_struct;
switch (options->encryption) { case FIL_ENCRYPTION_OFF: if (options->encryption_key_id != FIL_DEFAULT_ENCRYPTION_KEY) {
push_warning(
m_thd, Sql_condition::WARN_LEVEL_WARN,
HA_WRONG_CREATE_OPTION, "InnoDB: ENCRYPTED=NO implies" " ENCRYPTION_KEY_ID=1");
compile_time_assert(FIL_DEFAULT_ENCRYPTION_KEY == 1);
} /* m_trx is non-NULL because TRUNCATE that passesitsowntransaction.TRUNCATErecreates thetablepreservingtheoriginalENCRYPTED=NO attribute;bypasstheinnodb_encrypt_tables=FORCE checkheresotheencryptionstatedoesnot
silently change across a TRUNCATE. */ if (m_trx || srv_encrypt_tables != 2) { break;
}
push_warning(
m_thd, Sql_condition::WARN_LEVEL_WARN,
HA_WRONG_CREATE_OPTION, "InnoDB: ENCRYPTED=NO cannot be used with" " innodb_encrypt_tables=FORCE"); return"ENCRYPTED"; case FIL_ENCRYPTION_DEFAULT: if (!srv_encrypt_tables) { break;
} /* fall through */ case FIL_ENCRYPTION_ON: const uint32_t key_id = uint32_t(options->encryption_key_id); if (!encryption_key_id_exists(key_id)) {
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
HA_WRONG_CREATE_OPTION, "InnoDB: ENCRYPTION_KEY_ID %u not available",
key_id); return"ENCRYPTION_KEY_ID";
}
/* We do not support encryption for spatial indexes, exceptifinnodb_checksum_algorithm=full_crc32.
Do not allow ENCRYPTED=YES if any SPATIAL INDEX exists. */ if (options->encryption != FIL_ENCRYPTION_ON
|| srv_checksum_algorithm
>= SRV_CHECKSUM_ALGORITHM_FULL_CRC32) { break;
} for (ulint i = 0; i < m_form->s->keys; i++) { if (m_form->key_info[i].algorithm == HA_KEY_ALG_RTREE) {
push_warning(m_thd,
Sql_condition::WARN_LEVEL_WARN,
HA_ERR_UNSUPPORTED, "InnoDB: ENCRYPTED=YES is not" " supported for SPATIAL INDEX"); return"ENCRYPTED";
}
}
}
/** Parse the table name into normal name and remote path if needed. @param[in]nameTablename(db/tableorfullpath).
@return 0 if successful, otherwise, error number */ int
create_table_info_t::parse_table_name( constchar* #ifdef _WIN32
name #endif
)
{
DBUG_ENTER("parse_table_name");
#ifdef _WIN32 /* Names passed in from server are in two formats: 1.<database_name>/<table_name>:fornormaltablecreation 2.fullpath:fortemptablecreation,orDATADIRECTORY.
Whensrv_file_per_tableisonandmysqld_embeddedisoff, checkforfullpathpattern,i.e. X:\dir\...,Xisadriverletter,or \\dir1\dir2\...,UNCpath returnserrorifitisinfullpathformat,butnotcreatingatemp.
table. Currently InnoDB does not support symbolic link on Windows. */
if (m_innodb_file_per_table
&& !mysqld_embedded
&& !m_create_info->tmp_table()) {
/* Make sure DATA DIRECTORY is compatible with other options andsettheremotepath.Inthecaseofeither; CREATETEMPORARYTABLE...DATADIRECTORY={path}...; CREATETABLE...DATADIRECTORY={path}TABLESPACE={name}...;
we ignore the DATA DIRECTORY. */ if (m_create_info->data_file_name
&& m_create_info->data_file_name[0]
&& (my_use_symdir || m_create_info->recreate_identical_table)) { if (!create_option_data_directory_is_valid()) {
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
WARN_OPTION_IGNORED,
ER_DEFAULT(WARN_OPTION_IGNORED), "DATA DIRECTORY");
/* Check if there are any FTS indexes defined on this table. */ const uint fts_n_uniq= m_form->versioned() ? 2 : 1; for (uint i = 0; i < m_form->s->keys; i++) { const KEY* key = &m_form->key_info[i];
if (key->algorithm == HA_KEY_ALG_FULLTEXT) {
m_flags2 |= DICT_TF2_FTS;
/* We don't support FTS indexes in temporary
tables. */ if (is_temp) {
my_error(ER_NO_INDEX_ON_TEMPORARY, MYF(0), "FULLTEXT", "InnoDB");
DBUG_RETURN(false);
}
if (fts_doc_id_index_bad) { goto index_bad;
}
}
if (!key->name.streq(FTS_DOC_ID_INDEX)) { continue;
}
/* Do a pre-check on FTS DOC ID index */ if (!(key->flags & HA_NOSAME)
|| key->user_defined_key_parts != fts_n_uniq
|| (key->key_part[0].key_part_flag & HA_REVERSE_SORT)
|| strcmp(key->name.str, FTS_DOC_ID_INDEX.str)
|| strcmp(key->key_part[0].field->field_name.str,
FTS_DOC_ID.str)) {
fts_doc_id_index_bad = key->name.str;
}
/* Set the table flags */
dict_tf_set(&m_flags, innodb_row_format, zip_ssize,
m_use_data_dir, level && options->page_compressed, level);
if (m_form->s->table_type == TABLE_TYPE_SEQUENCE) {
m_flags |= DICT_TF_MASK_NO_ROLLBACK;
}
/* Set the flags2 when create table or alter tables */
m_flags2 |= DICT_TF2_FTS_AUX_HEX_NAME;
DBUG_RETURN(true);
}
/** Parse MERGE_THRESHOLD value from the string. @param[in]thdconnection @param[in]strstringwhichmightinclude'MERGE_THRESHOLD='
@return value parsed. 0 means not found or invalid value. */ static unsigned
innobase_parse_merge_threshold(
THD* thd, constchar* str)
{ staticconstchar* label = "MERGE_THRESHOLD="; staticconst size_t label_len = strlen(label); constchar* pos = str;
pos = strstr(str, label);
if (pos == NULL) { return(0);
}
pos += label_len;
lint ret = atoi(pos);
if (ret > 0 && ret <= 50) { return(static_cast<unsigned>(ret));
}
push_warning_printf(
thd, Sql_condition::WARN_LEVEL_WARN,
ER_ILLEGAL_HA_CREATE_OPTION, "InnoDB: Invalid value for MERGE_THRESHOLD in the CREATE TABLE" " statement. The value is ignored.");
return(0);
}
/** Parse hint for table and its indexes, and update the information indictionary. @param[in]thdconnection @param[in,out]tabletargettable
@param[in] table_share table definition */ void
innobase_parse_hint_from_comment(
THD* thd,
dict_table_t* table, const TABLE_SHARE* table_share)
{ unsigned merge_threshold_table; unsigned merge_threshold_index[MAX_KEY]; bool is_found[MAX_KEY];
for (uint i = 0; i < table_share->keys; i++) {
is_found[i] = false;
}
/* update in memory */ for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
index != NULL;
index = UT_LIST_GET_NEXT(indexes, index)) {
if (dict_index_is_auto_gen_clust(index)) {
/* GEN_CLUST_INDEX should use merge_threshold_table */
/* x-lock index is needed to exclude concurrent
pessimistic tree operations */
index->lock.x_lock(SRW_LOCK_CALL);
index->merge_threshold = merge_threshold_table
& ((1U << 6) - 1);
index->lock.x_unlock();
continue;
}
const Lex_cstring_strlen index_name(index->name); for (uint i = 0; i < table_share->keys; i++) { if (is_found[i]) { continue;
}
KEY* key_info = &table_share->key_info[i];
if (key_info->name.streq(index_name)) { /* x-lock index is needed to exclude concurrent
pessimistic tree operations */
index->lock.x_lock(SRW_LOCK_CALL);
index->merge_threshold
= merge_threshold_index[i]
& ((1U << 6) - 1);
index->lock.x_unlock();
is_found[i] = true;
break;
}
}
}
}
/** Set m_use_* flags. */ void
create_table_info_t::set_tablespace_type( bool table_being_altered_is_file_per_table)
{ /** Allow file_per_table for this table either because: 1)thesettinginnodb_file_per_table=on,
2) the table being altered is currently file_per_table */
m_allow_file_per_table =
m_innodb_file_per_table
|| table_being_altered_is_file_per_table;
/* Ignore the current innodb-file-per-table setting if we are
creating a temporary table. */
m_use_file_per_table = m_allow_file_per_table
&& !m_create_info->tmp_table();
/* DATA DIRECTORY must have m_use_file_per_table but cannot be
used with TEMPORARY tables. */
m_use_data_dir =
m_use_file_per_table
&& m_create_info->data_file_name
&& m_create_info->data_file_name[0]
&& (my_use_symdir || m_create_info->recreate_identical_table);
}
/** Initialize the create_table_info_t object.
@return error number */ int
create_table_info_t::initialize()
{
DBUG_ENTER("create_table_info_t::initialize");
if (m_form->s->fields > REC_MAX_N_USER_FIELDS) {
DBUG_RETURN(HA_ERR_TOO_MANY_FIELDS);
}
/* Check for name conflicts (with reserved name) for
any user indices to be created. */ if (innobase_index_name_is_reserved(m_thd, m_form->key_info,
m_form->s->keys)) {
DBUG_RETURN(HA_ERR_WRONG_INDEX);
}
/* Get the transaction associated with the current thd, or create one
if not yet created */
check_trx_exists(m_thd);
DBUG_RETURN(0);
}
/** Check if a virtual column is part of a fulltext or spatial index. */ bool
create_table_info_t::gcols_in_fulltext_or_spatial()
{ for (ulint i = 0; i < m_form->s->keys; i++) { const KEY* key = m_form->key_info + i; if (key->algorithm != HA_KEY_ALG_RTREE &&
key->algorithm != HA_KEY_ALG_FULLTEXT) { continue;
} for (ulint j = 0; j < key->user_defined_key_parts; j++) { /* We do not support special (Fulltext or
Spatial) index on virtual columns */ if (!key->key_part[j].field->stored_in_db()) {
my_error(ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN, MYF(0)); returntrue;
}
}
} returnfalse;
}
/** Prepare to create a new table to an InnoDB database. @param[in]nameTablename
@return error number */ int create_table_info_t::prepare_create_table(constchar* name, bool strict)
{
DBUG_ENTER("prepare_create_table");
/* Validate table options not handled by the SQL-parser */ if (check_table_options()) {
DBUG_RETURN(HA_WRONG_CREATE_OPTION);
}
/* Validate the create options if innodb_strict_mode is set. DonotusetheregularmessageforER_ILLEGAL_HA_CREATE_OPTION becauseInnoDBmightactuallysupporttheoption,butnotunder thecurrentconditions.Themessagesrevealingthespecific
problems are reported inside this function. */ if (strict && create_options_are_invalid()) {
DBUG_RETURN(HA_WRONG_CREATE_OPTION);
}
/* Create the table flags and flags2 */ if (!innobase_table_flags()) {
DBUG_RETURN(HA_WRONG_CREATE_OPTION);
}
if (high_level_read_only || recv_sys.rpo) {
DBUG_RETURN(HA_ERR_TABLE_READONLY);
}
if (gcols_in_fulltext_or_spatial()) {
DBUG_RETURN(HA_ERR_UNSUPPORTED);
}
for (uint i = 0; i < m_form->s->keys; i++) { const size_t max_field_len
= DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(m_flags); const KEY& key = m_form->key_info[i];
if (key.algorithm == HA_KEY_ALG_FULLTEXT) { continue;
}
if (too_big_key_part_length(max_field_len, key)) {
DBUG_RETURN(convert_error_code_to_mysql(
DB_TOO_BIG_INDEX_COL, m_flags, NULL));
}
}
DBUG_RETURN(parse_table_name(name));
}
/********************************************************************//**
Helper function to push warnings from InnoDB internals to SQL-layer. */ static void
ib_foreign_warn(
trx_t* trx, /*!< in: trx */
dberr_t error, /*!< in: error code to push as warning */ constchar *table_name, constchar *format,/*!< in: warning message */
...);
/** Push warning message to SQL-layer based on foreign key constraint index matcherror. @param[in]trxCurrenttransaction @param[in]operationOperation("Create"or"Alter") @param[in]create_nameTablenameasspecifiedinSQL @param[in]columnsForeignkeycolumnnamesarray @param[in]index_errorIndexerrorcode @param[in]err_colColumnwhereerrorhappened @param[in]err_indexIndexwhereerrorhappened
@param[in] table Table object */ staticvoid
foreign_push_index_error(trx_t* trx, constchar* operation, constchar* create_name, constchar* fk_text, constchar** columns, fkerr_t index_error,
ulint err_col, dict_index_t* err_index,
dict_table_t* table)
{ switch (index_error) { case FK_SUCCESS: break; case FK_INDEX_NOT_FOUND:
ib_foreign_warn(trx, DB_CANNOT_ADD_CONSTRAINT, create_name, "%s table %s with foreign key %s constraint" " failed. There is no index in the referenced" " table where the referenced columns appear" " as the first columns.",
operation, create_name, fk_text); return; case FK_IS_PREFIX_INDEX:
ib_foreign_warn(
trx, DB_CANNOT_ADD_CONSTRAINT, create_name, "%s table %s with foreign key %s constraint" " failed. There is only prefix index in the referenced" " table where the referenced columns appear" " as the first columns.",
operation, create_name, fk_text); return; case FK_COL_NOT_NULL:
ib_foreign_warn(
trx, DB_CANNOT_ADD_CONSTRAINT, create_name, "%s table %s with foreign key %s constraint" " failed. You have defined a SET NULL condition but " "column '%s' on index is defined as NOT NULL.",
operation, create_name, fk_text, columns[err_col]); return; case FK_COLS_NOT_EQUAL:
dict_field_t* field; constchar* col_name;
field = dict_index_get_nth_field(err_index, err_col);
col_name = field->col->is_virtual()
? "(null)"
: dict_table_get_col_name(
table, dict_col_get_no(field->col)).str;
ib_foreign_warn(
trx, DB_CANNOT_ADD_CONSTRAINT, create_name, "%s table %s with foreign key %s constraint" " failed. Field type or character set for column '%s' " "does not match referenced column '%s'.",
operation, create_name, fk_text, columns[err_col],
col_name); return;
}
DBUG_ASSERT("unknown error" == 0);
}
/** Find column or virtual column in table by its name. @param[in]tableTablewherecolumnissearched @param[in]nameNametosearchfor @retvaltrueiffound
@retval false if not found */ staticbool
find_col(dict_table_t* table, constchar** name)
{
ulint i; const Lex_ident_column outer_name = Lex_cstring_strlen(*name); for (i = 0; i < dict_table_get_n_cols(table); i++) {
if (outer_name.streq(inner_name)) { /* Found */
strcpy((char*)*name, inner_name.str); returntrue;
}
} returnfalse;
}
/** Foreign key printer for error messages. Prints FK name if it exists or
key part list in the form (col1, col2, col3, ...) */ class key_text
{ staticconst size_t MAX_TEXT = 48; char buf[MAX_TEXT + 1];
/** Find an auto-generated foreign key constraint identifier. @paramtableInnoDBtable
@return the next number to assign to a constraint */
ulint dict_table_get_foreign_id(const dict_table_t &table) noexcept
{
ulint id= 0;
for (const dict_foreign_t *foreign : table.foreign_set)
{ constchar *s= foreign->sql_id(); char *endp;
ulint f= strtoul(s, &endp, 10); if (!*endp && f > id)
id= f;
}
return id + 1;
}
/** Generate a foreign key constraint name for an anonymous constraint. @paramid_nrsequencetoallocateidentifiersfrom @paramnametablename
@param foreign foreign key */ void dict_create_add_foreign_id(ulint *id_nr, constchar *name,
dict_foreign_t *foreign) noexcept
{ if (!foreign->id)
{
size_t len= snprintf(nullptr, 0, "%s\377%zu", name, *id_nr);
foreign->id= static_cast<char*>(mem_heap_alloc(foreign->heap, len + 1));
snprintf(foreign->id, len + 1, "%s\377%zu", name, (*id_nr)++);
}
}
if (sqlcom == SQLCOM_ALTER_TABLE) {
mem_heap_t* heap = mem_heap_create(10000);
LEX_CSTRING t = innodb_convert_name(cs, m_form->s->table_name,
t_name);
LEX_CSTRING d = innodb_convert_name(cs, m_form->s->db, db_name);
dict_table_t* alter_table; char* n = dict_table_lookup(d, t, &alter_table, heap);
/* If we are altering a temporary table, the table name after ALTERTABLEdoesnotcorrespondtotheinternaltablename,and alter_table=nullptr.But,wedonotsupportFOREIGNKEY
constraints for temporary tables. */
if (alter_table) {
n = alter_table->name.m_name;
number = dict_table_get_foreign_id(*alter_table);
}
bufend = innobase_convert_name(
buf, MAX_TABLE_NAME_LEN,
foreign->referenced_table_name,
strlen(foreign->referenced_table_name), m_thd);
*bufend = '\0';
ib_foreign_warn(m_trx, DB_CANNOT_ADD_CONSTRAINT,
create_name, "%s table %s with foreign key %s " "constraint failed. Referenced table " "%s not found in the data dictionary.",
operation, create_name,
key_text(fk).str(), buf); return DB_CANNOT_ADD_CONSTRAINT;
}
/* Don't allow foreign keys on partitioned tables yet. */ if (foreign->referenced_table
&& dict_table_is_partition(foreign->referenced_table)) { /* How could one make a referenced table to be a
* partition? */
ut_ad(0);
my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING,
MYF(0), "FOREIGN KEY"); return (DB_CANNOT_ADD_CONSTRAINT);
}
col_it.init(fk->ref_columns); while ((col = col_it++)) {
ref_column_names[j] = mem_heap_strdupl(
foreign->heap, col->field_name.str,
col->field_name.length); if (foreign->referenced_table) {
success = find_col(foreign->referenced_table,
ref_column_names + j); if (!success) {
ib_foreign_warn(
m_trx,
DB_CANNOT_ADD_CONSTRAINT,
create_name, "%s table %s foreign key %s " "constraint failed. " "Column %s was not found.",
operation, create_name,
key_text(fk).str(),
ref_column_names[j]); return DB_CANNOT_ADD_CONSTRAINT;
}
}
++j;
} /* See ER_WRONG_FK_DEF in mysql_prepare_create_table() */
ut_ad(i == j);
/* Try to find an index which contains the columns as the first fieldsandintherightorder,andthetypesarethesameasin
foreign->foreign_index */
if (foreign->referenced_table) {
index = dict_foreign_find_index(
foreign->referenced_table, NULL,
ref_column_names, i, foreign->foreign_index, TRUE, FALSE, &index_error, &err_col,
&err_index);
foreign->referenced_col_names = static_cast<constchar**>(
mem_heap_alloc(foreign->heap, i * sizeof(void*))); if (!foreign->referenced_col_names) { return (DB_OUT_OF_MEMORY);
}
memcpy(foreign->referenced_col_names, ref_column_names,
i * sizeof(void*));
if (fk->delete_opt == FK_OPTION_SET_NULL
|| fk->update_opt == FK_OPTION_SET_NULL) { for (j = 0; j < foreign->n_fields; j++) { if ((dict_index_get_nth_col(
foreign->foreign_index, j)
->prtype)
& DATA_NOT_NULL) { const dict_col_t* col
= dict_index_get_nth_col(
foreign->foreign_index,
j); constchar* col_name
= dict_table_get_col_name(
foreign->foreign_index
->table,
dict_col_get_no(col)).str;
/* It is not sensible to define SET NULL ifthecolumnisnotallowedtobe
NULL! */
ib_foreign_warn(
m_trx,
DB_CANNOT_ADD_CONSTRAINT,
create_name, "%s table %s with foreign key " "%s constraint failed. You have" " defined a SET NULL condition " "but column '%s' is defined as " "NOT NULL.",
operation, create_name,
key_text(fk).str(), col_name);
return DB_CANNOT_ADD_CONSTRAINT;
}
}
} #ifdefined __GNUC__ && !defined __clang__ && __GNUC__ < 6 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" #endif switch (fk->delete_opt) { case FK_OPTION_UNDEF: case FK_OPTION_RESTRICT: break; case FK_OPTION_CASCADE:
foreign->type |= foreign->DELETE_CASCADE; break; case FK_OPTION_SET_NULL:
foreign->type |= foreign->DELETE_SET_NULL; break; case FK_OPTION_NO_ACTION:
foreign->type |= foreign->DELETE_NO_ACTION; break; case FK_OPTION_SET_DEFAULT: // TODO: MDEV-10393 Foreign keys SET DEFAULT action break; default:
ut_ad(0); break;
}
switch (fk->update_opt) { case FK_OPTION_UNDEF: case FK_OPTION_RESTRICT: break; case FK_OPTION_CASCADE:
foreign->type |= foreign->UPDATE_CASCADE; break; case FK_OPTION_SET_NULL:
foreign->type |= foreign->UPDATE_SET_NULL; break; case FK_OPTION_NO_ACTION:
foreign->type |= foreign->UPDATE_NO_ACTION; break; case FK_OPTION_SET_DEFAULT: // TODO: MDEV-10393 Foreign keys SET DEFAULT action break; default:
ut_ad(0); break;
} #ifdefined __GNUC__ && !defined __clang__ && __GNUC__ < 6 # pragma GCC diagnostic pop #endif
}
if (dict_foreigns_has_s_base_col(local_fk_set, table)) { return (DB_NO_FK_ON_S_BASE_COL);
}
/**********************************************************/ /* The following call adds the foreign key constraints
to the data dictionary system tables on disk */
m_trx->op_info = "adding foreign keys";
/** Create the internal innodb table. @paramcreate_fkwhethertoaddFOREIGNKEYconstraints
@param strict whether to give warnings for too big rows */ int create_table_info_t::create_table(bool create_fk, bool strict)
{ int error; int primary_key_no;
uint i; const ha_table_option_struct& o = *m_create_info->option_struct;
DBUG_ENTER("create_table");
/* Look for a primary key */
primary_key_no = (m_form->s->primary_key != MAX_KEY ?
(int) m_form->s->primary_key : -1);
/* Our function innobase_get_mysql_key_number_for_index assumes
the primary key is always number 0, if it exists */
ut_a(primary_key_no == -1 || primary_key_no == 0);
error = create_table_def();
if (error) {
DBUG_RETURN(error);
}
/* Create the keys */
if (m_form->s->keys == 0 || primary_key_no == -1) { /* Create an index which is used as the clustered index; ordertherowsbytheirrowidwhichisinternallygenerated
by InnoDB */
ulint flags = m_table->flags;
dict_index_t* index = dict_mem_index_create(
m_table, GEN_CLUST_INDEX.str,
DICT_CLUSTERED, 0);
error = convert_error_code_to_mysql(
row_create_index_for_mysql(
index, m_trx, NULL,
fil_encryption_t(o.encryption),
uint32_t(o.encryption_key_id)),
flags, m_thd); if (error) {
DBUG_RETURN(error);
}
}
if (primary_key_no != -1) { /* In InnoDB the clustered index must always be created
first */ if ((error = create_index(m_trx, m_form, o, m_table,
(uint) primary_key_no))) {
DBUG_RETURN(error);
}
}
/* Create the ancillary tables that are common to all FTS indexes on
this table. */ if (m_flags2 & DICT_TF2_FTS) {
fts_doc_id_index_enum ret;
/* Check whether there already exists FTS_DOC_ID_INDEX */
ret = innobase_fts_check_doc_id_index_in_def(
m_form->s->keys, m_form->key_info);
switch (ret) { case FTS_INCORRECT_DOC_ID_INDEX:
push_warning_printf(m_thd,
Sql_condition::WARN_LEVEL_WARN,
ER_WRONG_NAME_FOR_INDEX, " InnoDB: Index name %s is reserved" " for the unique index on" " FTS_DOC_ID column for FTS" " Document ID indexing" " on table %s. Please check" " the index definition to" " make sure it is of correct" " type",
FTS_DOC_ID_INDEX.str,
m_table->name.m_name);
if (m_table->fts) {
m_table->fts->~fts_t();
m_table->fts = nullptr;
}
my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0),
FTS_DOC_ID_INDEX.str);
DBUG_RETURN(-1); case FTS_EXIST_DOC_ID_INDEX: case FTS_NOT_EXIST_DOC_ID_INDEX: break;
}
for (i = 0; i < m_form->s->keys; i++) { if (i != uint(primary_key_no)
&& (error = create_index(m_trx, m_form, o, m_table, i))) {
DBUG_RETURN(error);
}
}
/* Cache all the FTS indexes on this table in the FTS specific
structure. They are used for FTS indexed column update handling. */ if (m_flags2 & DICT_TF2_FTS) {
fts_t* fts = m_table->fts;
/* Check that also referencing constraints are ok */
dict_names_t fk_tables;
mtr_t mtr{m_trx};
err = dict_load_foreigns(mtr, m_table_name, nullptr,
m_trx->id, true,
ignore_err, fk_tables); while (err == DB_SUCCESS && !fk_tables.empty()) {
dict_sys.load_table(
{fk_tables.front(), strlen(fk_tables.front())},
ignore_err);
fk_tables.pop_front();
}
}
switch (err) { case DB_PARENT_NO_INDEX:
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
HA_ERR_CANNOT_ADD_FOREIGN, "Create table '%s' with foreign key constraint" " failed. There is no index in the referenced" " table where the referenced columns appear" " as the first columns.", m_table_name); break;
case DB_CHILD_NO_INDEX:
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
HA_ERR_CANNOT_ADD_FOREIGN, "Create table '%s' with foreign key constraint" " failed. There is no index in the referencing" " table where referencing columns appear" " as the first columns.", m_table_name); break; case DB_NO_FK_ON_S_BASE_COL:
push_warning_printf(
m_thd, Sql_condition::WARN_LEVEL_WARN,
HA_ERR_CANNOT_ADD_FOREIGN, "Create table '%s' with foreign key constraint" " failed. Cannot add foreign key constraint" " placed on the base column of stored" " column. ",
m_table_name); default: break;
}
if (err != DB_SUCCESS) {
DBUG_RETURN(convert_error_code_to_mysql(
err, m_flags, NULL));
}
/* In TRUNCATE TABLE, we will merely warn about the maximum
row size being too large. */ if (!row_size_is_acceptable(*m_table, create_fk && strict)) {
DBUG_RETURN(convert_error_code_to_mysql(
DB_TOO_BIG_RECORD, m_flags, NULL));
}
/* maximum allowed size of a node pointer record */
ulint page_ptr_max; constbool comp= table->not_redundant(); /* table->space == NULL after DISCARD TABLESPACE */ const ulint zip_size= dict_tf_get_zip_size(table->flags);
record_size_info_t result;
if (zip_size && zip_size < srv_page_size)
{ /* On a ROW_FORMAT=COMPRESSED page, two records must fit in the uncompressedpagemodificationlog.Oncompressedpages withsize.physical()==univ_page_size.physical(),
this limit will never be reached. */
ut_ad(comp); /* The maximum allowed record size is the size of anemptypage,minusabyteforrecodingtheheap numberinthepagemodificationlog.Themaximum
allowed node pointer size is half that. */
result.max_leaf_size= page_zip_empty_size(n_fields, zip_size); if (result.max_leaf_size)
{
result.max_leaf_size--;
}
page_ptr_max= result.max_leaf_size / 2; /* On a compressed page, there is a two-byte entry in thedensepagedirectoryforeveryrecord.Butthere
is no record header. */
result.shortest_size= 2;
} else
{ /* The maximum allowed record size is half a B-tree page(16kfor64kpagesize).Noadditionalsparse pagedirectoryentrywillbegeneratedforthefirst
few user records. */
result.max_leaf_size= (comp || srv_page_size < UNIV_PAGE_SIZE_MAX)
? page_get_free_space_of_empty(comp) / 2
: REDUNDANT_REC_MAX_DATA_SIZE;
page_ptr_max= result.max_leaf_size; /* Each record has a header. */
result.shortest_size= comp ? REC_N_NEW_EXTRA_BYTES : REC_N_OLD_EXTRA_BYTES;
}
if (comp)
{ /* Include the "null" flags in the
maximum possible record size. */
result.shortest_size+= UT_BITS_IN_BYTES(n_nullable);
} else
{ /* For each column, include a 2-byte offset and a "null"flag.The1-byteformatisonlyusedinshort recordsthatdonotcontainexternallystoredcolumns. Suchrecordscouldneverexceedthepagelimit,even
when using the 2-byte format. */
result.shortest_size+= 2 * n_fields;
}
/* Compute the maximum possible record size. */ for (unsigned i= 0; i < n_fields; i++)
{ const dict_field_t &f= fields[i]; const dict_col_t &col= *f.col;
/* In dtuple_convert_big_rec(), variable-length columns thatarelongerthanBTR_EXTERN_LOCAL_STORED_MAX_SIZE maybechosenforexternalstorage.
Fixed-lengthcolumns,andallcolumnsofsecondary
index records are always stored inline. */
/* Determine the maximum length of the index field. Thefield_ext_max_sizeshouldbecomputedastheworst caseinrec_get_converted_size_comp()for
REC_STATUS_ORDINARY records. */
size_t field_max_size= dict_col_get_fixed_size(&col, comp); if (field_max_size && f.fixed_len != 0)
{ /* dict_index_add_col() should guarantee this */
ut_ad(!f.prefix_len || f.fixed_len == f.prefix_len); if (f.prefix_len)
field_max_size= f.prefix_len; /* Fixed lengths are not encoded
in ROW_FORMAT=COMPACT. */ goto add_field_size;
}
field_max_size= dict_col_get_max_size(&col);
if (f.prefix_len)
{ if (f.prefix_len < field_max_size)
{
field_max_size= f.prefix_len;
}
/* those conditions were copied from dtuple_convert_big_rec()*/
} elseif (field_max_size > max_local_len &&
field_max_size > BTR_EXTERN_LOCAL_STORED_MAX_SIZE &&
DATA_BIG_COL(&col) && dict_index_is_clust(this))
{
/* In the worst case, we have a locally stored columnofBTR_EXTERN_LOCAL_STORED_MAX_SIZEbytes. Thelengthcanbestoredinonebyte.Ifthe columnwerestoredexternally,thelengthsin theclusteredindexpagewouldbe
BTR_EXTERN_FIELD_REF_SIZE and 2. */
field_max_size= max_local_len;
}
if (comp)
{ /* Add the extra size for ROW_FORMAT=COMPACT. ForROW_FORMAT=REDUNDANT,thesebyteswere
added to result.shortest_size before this loop. */
result.shortest_size+= field_max_size < 256 ? 1 : 2;
}
add_field_size:
result.shortest_size+= field_max_size;
/* Check the size limit on leaf pages. */ if (result.shortest_size >= result.max_leaf_size)
{
result.set_too_big(i);
}
/* Check the size limit on non-leaf pages. Records storedinnon-leafB-treepagesconsistoftheunique columnsoftherecord(thekeycolumnsoftheB-tree) andanodepointerfield.Whenwehaveprocessedthe uniquecolumns,result.shortest_sizeequalsthesizeofthe
node pointer record minus the node pointer column. */ if (i + 1 == dict_index_get_n_unique_in_tree(this) &&
result.shortest_size + REC_NODE_PTR_SIZE + (comp ? 0 : 2) >=
page_ptr_max)
{
result.set_too_big(i);
}
}
return result;
}
/** Issue a warning that the row is too big. */ staticvoid ib_warn_row_too_big(THD *thd, const dict_table_t *table)
{ /* FIXME: this row size check should be improved */ /* If prefix is true then a 768-byte prefix is stored
locally for BLOB fields. Refer to dict_table_get_format() */ constbool prefix= !dict_table_has_atomic_blobs(table);
push_warning_printf(
thd, Sql_condition::WARN_LEVEL_WARN, HA_ERR_TO_BIG_ROW, "Row size too large (> " ULINTPF "). Changing some columns to TEXT" " or BLOB %smay help. In current row format, BLOB prefix of" " %d bytes is stored inline.",
free_space,
prefix ? "or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED " : "",
prefix ? DICT_MAX_FIXED_COL_LEN : 0);
}
bool create_table_info_t::row_size_is_acceptable( const dict_index_t &index, bool strict) const
{ if ((index.type & DICT_FTS) || index.table->is_system_db)
{ /* Ignore system tables check because innodb_table_stats
maximum row size can not fit on 4k page. */ returntrue;
}
ut_ad((!field->name) == field->col->is_dropped()); if (innodb_strict_mode || global_system_variables.log_warnings > 2)
{
ib::error_or_warn eow(strict && innodb_strict_mode); if (field->name)
eow << "Cannot add field " << field->name << " in table "; else
eow << "Cannot add an instantly dropped column in table ";
eow << "`" << m_form->s->db.str << "`.`" << m_form->s->table_name.str
<< "`"" because after adding it, the row size is "
<< info.get_overrun_size()
<< " which is greater than maximum allowed size ("
<< info.max_leaf_size << " bytes) for a record on index leaf page.";
}
if (!table->is_temporary())
{ constunsigned col_no= innodb_col_no(ai);
table->persistent_autoinc= static_cast<uint16_t>
(dict_table_get_nth_col_pos(table, col_no, nullptr) + 1) &
dict_index_t::MAX_N_FIELDS; /* Persist the "last used" value, which typically is AUTO_INCREMENT - 1.
In btr_create(), the value 0 was already written. */ if (--autoinc)
btr_write_autoinc(trx, dict_table_get_first_index(table), autoinc);
}
if (!(info.flags2() & DICT_TF2_TEMPORARY))
{
trx_start_for_ddl(trx); if (dberr_t err= lock_sys_tables(trx))
error= convert_error_code_to_mysql(err, 0, nullptr);
}
row_mysql_lock_data_dictionary(trx);
}
if (!error) /* We can't possibly have foreign key information when creating a stubtableforimporting.frm/.cfg/.ibdbecauseitisnot
stored in any of these files. */
error= info.create_table(own_trx, !create_info->recreate_identical_table);
if (own_trx)
{
row_mysql_unlock_data_dictionary(trx);
if (!error)
{ /* Skip stats update when creating a stub table for importing, asitisnotneededandwouldreporterrorduetothetable
not being readable yet. */ if (!info.creating_stub())
dict_stats_empty_table_and_save(info.table()); if (!info.table()->is_temporary())
log_write_up_to(trx->commit_lsn, true);
info.table()->release();
}
trx->clear_and_free();
}
} elseif (!error && m_prebuilt)
m_prebuilt->table= info.table();
/* Obtain an exclusive lock on the table. */
dberr_t err = lock_table_for_trx(m_prebuilt->table,
m_prebuilt->trx, LOCK_X); if (err == DB_SUCCESS) {
err = lock_sys_tables(m_prebuilt->trx);
}
if (err != DB_SUCCESS) { /* unable to lock the table: do nothing */
m_prebuilt->trx->commit();
} elseif (discard) {
/* Discarding an already discarded tablespace should be an idempotentoperation.Also,ifthe.ibdfileismissingthe usermaywanttosettheDISCARDflaginordertoIMPORT
a new tablespace. */
if (!m_prebuilt->table->is_readable()) {
ib_senderrf(
m_prebuilt->trx->mysql_thd,
IB_LOG_LEVEL_WARN, ER_TABLESPACE_MISSING,
m_prebuilt->table->name.m_name);
}
err = row_discard_tablespace_for_mysql(
m_prebuilt->table, m_prebuilt->trx);
} elseif (m_prebuilt->table->is_readable()) { /* Commit the transaction in order to
release the table lock. */
trx_commit_for_mysql(m_prebuilt->trx);
ib::error() << "Unable to import tablespace "
<< m_prebuilt->table->name << " because it already" " exists. Please DISCARD the tablespace" " before IMPORT.";
ib_senderrf(
m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR,
ER_TABLESPACE_EXISTS, m_prebuilt->table->name.m_name);
if (dberr_t ret =
dict_stats_update_persistent_try(m_prebuilt->trx, t)) {
push_warning_printf(
ha_thd(),
Sql_condition::WARN_LEVEL_WARN,
ER_ALTER_INFO, "Error updating stats after" " ALTER TABLE %.*sQ.%sQ IMPORT TABLESPACE: %s", int(t->name.dblen()), t->name.m_name,
t->name.basename(), ut_strerr(ret));
}
DBUG_RETURN(0);
}
/** Report a DROP TABLE failure due to a FOREIGN KEY constraint. @paramnametablename
@param foreign constraint */
ATTRIBUTE_COLD staticvoid delete_table_cannot_drop_foreign(const table_name_t &name, const dict_foreign_t &foreign)
{
mysql_mutex_lock(&dict_foreign_err_mutex);
rewind(dict_foreign_err_file);
ut_print_timestamp(dict_foreign_err_file);
fputs(" Cannot drop table ", dict_foreign_err_file);
ut_print_name(dict_foreign_err_file, nullptr, name.m_name);
fputs("\nbecause it is referenced by ", dict_foreign_err_file);
ut_print_name(dict_foreign_err_file, nullptr, foreign.foreign_table_name);
putc('\n', dict_foreign_err_file);
mysql_mutex_unlock(&dict_foreign_err_mutex);
}
/** Check if DROP TABLE would fail due to a FOREIGN KEY constraint. @paramtabletabletobedropped @paramsqlcomthd_sql_command(current_thd)
@return whether child tables that refer to this table exist */ staticbool delete_table_check_foreigns(const dict_table_t &table,
enum_sql_command sqlcom)
{ constbool drop_db{sqlcom == SQLCOM_DROP_DB}; for (constauto foreign : table.referenced_set)
{ /* We should allow dropping a referenced table if creating thatreferencedtablehasfailedforsomereason.Forexample ifreferencedtableiscreatedbutitcolumntypesthatare
referenced do not match. */ if (foreign->foreign_table == &table ||
(drop_db &&
dict_tables_have_same_db(table.name.m_name,
foreign->foreign_table_name_lookup))) continue;
delete_table_cannot_drop_foreign(table.name, *foreign); returntrue;
}
returnfalse;
}
/** DROP TABLE (possibly as part of DROP DATABASE, CREATE/ALTER TABLE) @paramnametablename
@return error number */ int ha_innobase::delete_table(constchar *name)
{
DBUG_ENTER("ha_innobase::delete_table"); if (high_level_read_only || recv_sys.rpo)
DBUG_RETURN(HA_ERR_TABLE_READONLY);
if (parent_trx->check_foreigns &&
delete_table_check_foreigns(*table, sqlcom))
{
dict_sys.unlock();
DBUG_RETURN(HA_ERR_ROW_IS_REFERENCED);
}
table->acquire();
dict_sys.unlock();
trx_t *trx= parent_trx;
dberr_t err= DB_SUCCESS; if (!trx->lock.table_locks.empty() &&
thd_ddl_options(trx->mysql_thd)->is_create_select())
{ /* CREATE TABLE...PRIMARY KEY...SELECT ought to be dropping the tablebecauseaduplicatekeywasdetectedoratimeoutoccurred.
Weshallhijacktheexistingtransactiontodropthetableand committhetransaction.Ifthisisapartitionedtable,one partitionwillusethishijackedtransaction;otherswillusea
separate transaction, one per partition. */
ut_ad(!trx->dict_operation_lock_mode);
ut_ad(trx->will_lock);
ut_ad(trx->state == TRX_STATE_ACTIVE);
trx->dict_operation= true;
} else
{
trx= innobase_trx_allocate(thd);
trx_start_for_ddl(trx);
if (table->name.is_temporary()) /* There is no need to lock any FOREIGN KEY child tables. */; #ifdef WITH_PARTITION_STORAGE_ENGINE elseif (table->name.part()) /* FOREIGN KEY constraints cannot exist on partitioned tables. */; #endif else
err= lock_table_children(table, trx);
}
if (err == DB_SUCCESS)
err= lock_table_for_trx(table, trx, LOCK_X);
if (rollback_add_partition)
{ if (!fts)
purge_sys.stop_FTS(); /* This looks like the rollback of ALTER TABLE...ADD PARTITION thatwascausedbyMDLtimeout.Wecouldhavewrittenundolog
for inserting the data into the new partitions. */ if (!(table->stat & dict_table_t::STATS_PERSISTENT_OFF))
{ /* We do not really know if we are holding MDL_EXCLUSIVE. Even thoughthiscodeishandlingthecasethatwearenotholding it,wemightactuallyholdit.Wewanttoavoidadeadlock
with dict_stats_process_entry_from_recalc_pool(). */
dict_stats_recalc_pool_del(table->id, true); /* If statistics calculation is still using this table, we will
catch it below while waiting for purge to stop using this table. */
}
} #endif
if (err != DB_SUCCESS && skip_wait)
{ /* We may skip deleting statistics if we cannot lock the tables,
when the table carries a temporary name. */
ut_ad(err == DB_LOCK_WAIT);
ut_ad(trx->error_state == DB_SUCCESS);
err= DB_SUCCESS;
stats.close();
stats_failed= true;
}
}
if (!table->release() && err == DB_SUCCESS)
{ /* Wait for purge threads to stop using the table. */ for (uint n= 15;;)
{
dict_sys.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
dict_sys.lock(SRW_LOCK_CALL);
if (!--n)
{
err= DB_LOCK_WAIT_TIMEOUT; break;
} if (!table->get_ref_count()) break;
}
}
trx->dict_operation_lock_mode= true;
if (err != DB_SUCCESS)
{
err_exit:
trx->rollback(); switch (err) { case DB_CANNOT_DROP_CONSTRAINT: case DB_LOCK_WAIT_TIMEOUT: break; default:
ib::error() << "DROP TABLE " << table->name << ": " << err;
} if (fts)
{
fts_optimize_add_table(table);
purge_sys.resume_FTS();
} #ifdef WITH_PARTITION_STORAGE_ENGINE elseif (rollback_add_partition)
purge_sys.resume_FTS(); #endif
row_mysql_unlock_data_dictionary(trx); if (trx != parent_trx)
trx->free(); if (!stats_failed)
stats.close();
DBUG_RETURN(convert_error_code_to_mysql(err, 0, NULL));
}
if (!table->no_rollback())
{ if (trx->check_foreigns && delete_table_check_foreigns(*table, sqlcom))
{
err= DB_CANNOT_DROP_CONSTRAINT; goto err_exit;
}
err= trx->drop_table_foreign(table->name);
}
if (err == DB_SUCCESS && !stats_failed)
err= trx->drop_table_statistics(table->name); if (err != DB_SUCCESS) goto err_exit;
err= trx->drop_table(*table); if (err != DB_SUCCESS) goto err_exit;
std::vector<pfs_os_file_t> deleted;
trx->commit(deleted);
row_mysql_unlock_data_dictionary(trx); if (!stats_failed)
stats.close(); for (pfs_os_file_t d : deleted)
os_file_close(d);
log_write_up_to(trx->commit_lsn, true);
trx->commit_lsn= 0; if (trx != parent_trx)
trx->free(); if (!fts) #ifdef WITH_PARTITION_STORAGE_ENGINE if (!rollback_add_partition) #endif
DBUG_RETURN(0);
purge_sys.resume_FTS();
DBUG_RETURN(0);
}
if (error != DB_SUCCESS) { if (error == DB_TABLE_NOT_FOUND
&& lower_case_table_names == 1) { constchar* is_part = dict_is_partition(norm_from);
if (is_part) { char par_case_name[FN_REFLEN]; /* Check for the table using lower casename,includingthepartition
separator "P" */
system_charset_info->casedn_z(
norm_from, strlen(norm_from),
par_case_name, sizeof(par_case_name));
trx_start_if_not_started(trx, true);
error = row_rename_table_for_mysql(
par_case_name, norm_to, trx,
RENAME_IGNORE_FK);
}
}
if (error == DB_SUCCESS) {
sql_print_warning("Rename partition table %s" " succeeds after converting to lower" " case. The table may have" " been moved from a case" " in-sensitive file system.\n",
norm_from);
}
}
DBUG_RETURN(error);
}
/** TRUNCATE TABLE @returnerrorcode
@retval 0 on success */ int ha_innobase::truncate()
{
DBUG_ENTER("ha_innobase::truncate");
update_thd();
#ifdef UNIV_DEBUG if (!thd_test_options(m_user_thd, OPTION_NO_FOREIGN_KEY_CHECKS))
{ /* fk_truncate_illegal_if_parent() should have failed in Sql_cmd_truncate_table::handler_truncate()ifforeign_key_checks=ON
and child tables exist. */
dict_sys.freeze(SRW_LOCK_CALL); for (constauto foreign : m_prebuilt->table->referenced_set)
ut_ad(foreign->foreign_table == m_prebuilt->table);
dict_sys.unfreeze();
} #endif
if (int err= is_valid_trx())
DBUG_RETURN(err);
HA_CREATE_INFO info;
dict_table_t *ib_table= m_prebuilt->table;
info.init();
update_create_info_from_table(&info, table); switch (dict_tf_get_rec_format(ib_table->flags)) { case REC_FORMAT_REDUNDANT:
info.row_type= ROW_TYPE_REDUNDANT; break; case REC_FORMAT_COMPACT:
info.row_type= ROW_TYPE_COMPACT; break; case REC_FORMAT_COMPRESSED:
info.row_type= ROW_TYPE_COMPRESSED; break; case REC_FORMAT_DYNAMIC:
info.row_type= ROW_TYPE_DYNAMIC; break;
}
info.option_struct= option_struct;
if (ib_table->is_temporary())
{
info.options|= HA_LEX_CREATE_TMP_TABLE;
/* Validate the create options before dropping the existing table,sothatavalidationfailureleavestheoriginaltable intactinsteadofdroppingitandthenfailingincreate(),
which would leave the handler without a table. */
{
create_table_info_t validate(m_user_thd, table, &info, true, trx); int err= validate.initialize(); if (!err)
err= validate.prepare_create_table(ib_table->name.m_name, false); if (err)
{
trx_rollback_for_mysql(trx);
trx->free();
DBUG_RETURN(err);
}
}
if (error == DB_SUCCESS)
{ /* Wait for purge threads to stop using the table. */ for (uint n = 15; ib_table->get_ref_count() > 1; )
{ if (!--n)
{
error= DB_LOCK_WAIT_TIMEOUT; break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
err= open(name, 0, 0); if (!err)
{
m_prebuilt->stored_select_lock_type= stored_lock;
m_prebuilt->table->update_time= update_time;
row_prebuilt_free(prebuilt);
my_free(upd_buf);
} else
{ /* Revert to the old table. */
m_prebuilt= prebuilt;
m_upd_buf= upd_buf;
m_upd_buf_size= upd_buf_size;
}
}
trx->clear_and_free(); if (!stats_failed)
stats.close();
mem_heap_free(heap);
DBUG_RETURN(err);
}
/** Deinitialize InnoDB persistent statistics, forcing them tobereloadedonsubsequentha_innobase::open().
@param t table for which the cached STATS_PERSISTENT are to be evicted */ staticvoid stats_deinit(dict_table_t *t) noexcept
{
ut_ad(dict_sys.frozen());
ut_ad(t->get_ref_count() == 0);
if (t->is_temporary() || t->no_rollback()) return;
/*********************************************************************//**
Renames an InnoDB table.
@return0or error code */
int
ha_innobase::rename_table( /*======================*/ constchar* from, /*!< in: old name of the table */ constchar* to) /*!< in: new name of the table */
{
THD* thd = ha_thd();
DBUG_ENTER("ha_innobase::rename_table");
if (high_level_read_only || recv_sys.rpo) {
ib_senderrf(thd, IB_LOG_LEVEL_WARN, ER_READ_ONLY_MODE);
DBUG_RETURN(HA_ERR_TABLE_READONLY);
}
if (error == DB_SUCCESS) {
error = innobase_rename_table(trx, from, to,
RENAME_ALTER_COPY);
}
DEBUG_SYNC(thd, "after_innobase_rename_table");
if (error == DB_SUCCESS && !stats_fail) {
error = dict_stats_rename_table(norm_from, norm_to, trx); if (error == DB_DUPLICATE_KEY) { /* The duplicate may also occur in
mysql.innodb_index_stats. */
my_error(ER_DUP_KEY, MYF(0), "mysql.innodb_table_stats");
error = DB_ERROR;
}
}
if (error == DB_SUCCESS) {
trx->flush_log_later = true; if (t) {
ut_ad(dict_sys.locked()); if (fts_exist) {
fts_optimize_add_table(t);
} if (UNIV_LIKELY(t->release())) {
stats_deinit(t);
} else {
ut_ad("unexpected references" == 0);
}
}
innobase_commit_low(trx);
} else { if (t) { if (fts_exist) {
fts_optimize_add_table(t);
}
t->release();
}
trx->rollback();
}
row_mysql_unlock_data_dictionary(trx);
if (fts_exist || pause_purge) {
purge_sys.resume_FTS();
}
if (error == DB_SUCCESS) {
log_write_up_to(trx->commit_lsn, true);
}
trx->flush_log_later = false;
trx->clear_and_free(); if (!stats_fail) {
stats.close();
}
switch (error) { case DB_SUCCESS:
DBUG_RETURN(0); case DB_DUPLICATE_KEY: /* We are not able to deal with handler::get_dup_key() duringDDLoperations,becausetheduplicatekeywould
exist in metadata tables, not in the user table. */
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), to);
DBUG_RETURN(HA_ERR_GENERIC); case DB_LOCK_WAIT_TIMEOUT:
my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
DBUG_RETURN(HA_ERR_GENERIC); case DB_FOREIGN_DUPLICATE_KEY:
my_error(ER_DUP_CONSTRAINT_NAME, MYF(0), "FOREIGN KEY", "");
DBUG_RETURN(HA_ERR_GENERIC); default:
DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL));
}
}
/*********************************************************************//**
Estimates the number of index records in a range.
@return estimated number of rows */
ha_rows
ha_innobase::records_in_range( /*==========================*/
uint keynr, /*!< in: index number */ const key_range *min_key, /*!< in: start key value of the
range, may also be 0 */ const key_range *max_key, /*!< in: range end key val, may
also be 0 */
page_range *pages)
{
KEY* key;
dict_index_t* index;
dtuple_t* range_start;
dtuple_t* range_end;
ha_rows n_rows = HA_POS_ERROR;
page_cur_mode_t mode1;
page_cur_mode_t mode2;
mem_heap_t* heap;
DBUG_ENTER("records_in_range");
ut_ad(m_prebuilt->trx == thd_to_trx(ha_thd()));
m_prebuilt->trx->op_info = "estimating records in index range";
active_index = keynr;
key = table->key_info + active_index;
index = innobase_get_index(keynr);
/* There exists possibility of not being able to find requested indexduetoinconsistencybetweenMySQLandInoDBdictionaryinfo.
Necessary message should have been printed in innobase_get_index() */ if (!index || !m_prebuilt->table->space) {
func_exit:
m_prebuilt->trx->op_info = "";
DBUG_RETURN((ha_rows) n_rows);
} if (index->is_corrupted()) {
n_rows = HA_ERR_INDEX_CORRUPT; goto func_exit;
} if (!row_merge_is_index_usable(m_prebuilt->trx, index)) {
n_rows = HA_ERR_TABLE_DEF_CHANGED; goto func_exit;
}
/* The MariaDB optimizer seems to believe an estimate of 0 rows is alwaysaccurateandmayreturntheresult'Emptyset'basedonthat. Theaccuracyisnotguaranteed,andevenifitwere,foralocking readweshouldanywayperformthesearchtosetthenext-keylock.
Add 1 to the value to make sure MySQL does not make the assumption! */
if (n_rows == 0) {
n_rows = 1;
}
goto cleanup;
}
/*********************************************************************//**
Gives an UPPER BOUND to the number of rows in a table. This is used in
filesort.cc.
@return upper bound of rows */
/* Calculate a minimum length for a clustered index record and from thatanupperboundforthenumberofrows.Sinceweonlycalculate newstatisticsinrow0mysql.ccwhenatablehasgrownbyathreshold
factor, we must add a safety factor 2 in front of the formula below. */
/* Set num_rows less than MERGEBUFF to simulate the case where we do
not have enough space to merge the externally sorted file blocks. */
DBUG_EXECUTE_IF("set_num_rows_lt_MERGEBUFF",
estimate = 2;
DBUG_SET("-d,set_num_rows_lt_MERGEBUFF");
);
DBUG_RETURN((ha_rows) estimate);
}
/*********************************************************************//**
How many seeks it will take to read through the table. This is to be
comparable to the number returned by records_in_range so that we can
decide if we should scan the table or use keys.
@return estimated time measured in disk seeks */
#ifdef NOT_USED
IO_AND_CPU_COST
ha_innobase::scan_time() /*====================*/
{ /* Since MySQL seems to favor table scans too much over index searches,wepretendthatasequentialreadtakesthesametime asarandomdiskread,thatis,wedonotdividethefollowing
by 10, which would be physically realistic. */
/* The locking below is disabled for performance reasons. Without itwecouldendupreturninguninitializedvaluetothecaller, whichintheworstcasecouldmakesomequeryplangobogusor
issue a Valgrind warning. */ if (m_prebuilt == NULL) { /* In case of derived table, Optimizer will try to fetch stat fortableevenbeforetableiscreateoropen.Insuch casesreturndefaultvalueof1. TODO:Thiswillbefurtherimprovedtoreturnsomeapproximate estimatebutthatwouldalsoneedspre-populationofstats
structure. As of now approach is in sync with MyISAM. */ return { (ulonglong2double(stats.data_file_length) / IO_SIZE * DISK_READ_COST), 0.0 };
}
/******************************************************************//**
Calculate the time it takes to read a set of ranges through an index This enables us to optimise reads for clustered indexes.
@return estimated time measured in disk seeks */
#ifdef NOT_USED double
ha_innobase::read_time( /*===================*/
uint index, /*!< in: key number */
uint ranges, /*!< in: how many ranges */
ha_rows rows) /*!< in: estimated number of rows in the ranges */
{
ha_rows total_rows;
if (index != table->s->primary_key) { /* Not clustered */ return(handler::read_time(index, ranges, rows));
}
/* Assume that the read time is proportional to the scan time for all
rows + at most one seek per range. */
double time_for_scan = scan_time();
if ((total_rows = estimate_rows_upper_bound()) < rows) {
/*********************************************************************//**
Calculates the key number used inside MySQL for an Innobase index.
@return the key number used inside MySQL */ static unsigned
innobase_get_mysql_key_number_for_index( /*====================================*/ const TABLE* table, /*!< in: table in MySQL data
dictionary */
dict_table_t* ib_table,/*!< in: table in InnoDB data
dictionary */ const dict_index_t* index) /*!< in: index */
{ const dict_index_t* ind; unsignedint i;
/* If index does not belong to the table object of share structure (ib_tablecomesfromthesharestructure)searchtheindex->table
object instead */ if (index->table != ib_table) {
i = 0;
ind = dict_table_get_first_index(index->table); constbool auto_gen_clust = dict_index_is_auto_gen_clust(ind);
while (index != ind) {
ind = dict_table_get_next_index(ind);
i++;
}
if (auto_gen_clust) {
ut_a(i > 0);
i--;
}
return(i);
}
/* Directly find matching index with information from mysql TABLE
structure and InnoDB dict_index_t list */ for (i = 0; i < table->s->keys; i++) {
ind = dict_table_get_index_on_name(
ib_table, table->key_info[i].name.str);
if (index == ind) { return(i);
}
}
/* Loop through each index of the table and lock them */ for (ind = dict_table_get_first_index(ib_table);
ind != NULL;
ind = dict_table_get_next_index(ind)) { if (index == ind) { /* Temp index is internal to InnoDB, that is notpresentintheMySQLindexlist,sono
need to print such mismatch warning. */ if (index->is_committed()) {
sql_print_warning( "Found index %s in InnoDB index list" " but not its MariaDB index number." " It could be an InnoDB internal" " index.",
index->name());
} return(~0U);
}
}
ut_error;
return(~0U);
}
/*********************************************************************//**
Calculate Record Per Key value. Need to exclude the NULL value if
innodb_stats_method is set to "nulls_ignored"
@return estimated record per key value */
rec_per_key_t
innodb_rec_per_key( /*===============*/
dict_index_t* index, /*!< in: dict_index_t structure */
ulint i, /*!< in: the column we are
calculating rec per key */
ha_rows records) /*!< in: estimated total records */
{
rec_per_key_t rec_per_key;
ib_uint64_t n_diff;
if (records == 0) { /* "Records per key" is meaningless for empty tables.
Return 1.0 because that is most convenient to the Optimizer. */ return(1.0);
}
/* In theory, index->stat_n_non_null_key_vals[i] shouldalwaysbelessthanthenumberofrecords. Sincethisisstatisticsvalue,thevaluecould haveslightdiscrepancy.Butwewillmakesure
the number of null values is not a negative number. */ if (records < n_non_null) {
n_null = 0;
} else {
n_null = records - n_non_null;
}
/* If the number of NULL values is the same as or largerthanthatofthedistinctvalues,wecould considerthatthetableconsistsmostlyofNULLvalue.
Set rec_per_key to 1. */ if (n_diff <= n_null) {
rec_per_key = 1.0;
} else { /* Need to exclude rows with NULL values from
rec_per_key calculation */
rec_per_key
= static_cast<rec_per_key_t>(records - n_null)
/ static_cast<rec_per_key_t>(n_diff - n_null);
}
} else {
DEBUG_SYNC_C("after_checking_for_0");
rec_per_key = static_cast<rec_per_key_t>(records)
/ static_cast<rec_per_key_t>(n_diff);
}
if (rec_per_key < 1.0) { /* Values below 1.0 are meaningless and must be due to the
stats being imprecise. */
rec_per_key = 1.0;
}
return(rec_per_key);
}
/** Calculate how many KiB of new data we will be able to insert to the tablespacewithoutrunningoutofspace.Startwithaspaceobjectthathas beenacquiredbythecallerwhoholdsitforthecalculation, @param[in]spacetablespaceobjectfromfil_space_acquire()
@return available space in KiB */ static uintmax_t
fsp_get_available_space_in_free_extents(const fil_space_t& space)
{
ulint size_in_header = space.size_in_header; if (size_in_header < FSP_EXTENT_SIZE) { return0; /* TODO: count free frag pages and
return a value based on that */
}
/* Below we play safe when counting free extents above the free limit: someofthemwillcontainextentdescriptorpages,andtherefore
will not be free extents */
ut_ad(size_in_header >= space.free_limit);
ulint n_free_up =
(size_in_header - space.free_limit) / FSP_EXTENT_SIZE;
/* We reserve 1 extent + 0.5 % of the space size to undo logs and1extent+0.5%tocleaningoperations;NOTE:thissource
code is duplicated in the function above! */
/*********************************************************************//**
Returns statistics information of the table to the MySQL interpreter,
in various fields of the handle object.
@return HA_ERR_* error code or0 */
TRANSACTIONAL_TARGET int
ha_innobase::info_low( /*==================*/
uint flag, /*!< in: what information is requested */ bool is_analyze)
{
dict_table_t* ib_table;
ib_uint64_t n_rows; char path[FN_REFLEN];
os_file_stat_t stat_info;
DBUG_ENTER("info");
ut_ad(!srv_read_only_mode || recv_sys.rpo);
DEBUG_SYNC_C("ha_innobase_info_low");
/* If we are forcing recovery at a high level, we will suppress statisticscalculationontables,becausethatmaycrashthe
server if an index is badly corrupted. */
/* We do not know if MySQL can call this function before calling external_lock().Tobesafe,updatethethdofthecurrenttable
handle. */
update_thd(ha_thd());
m_prebuilt->trx->op_info = "returning various info to MariaDB";
if (ib_table->stats_is_persistent()
&& !recv_sys.rpo
&& dict_stats_persistent_storage_check(false)
== SCHEMA_OK) { if (is_analyze) {
dict_stats_recalc_pool_del(ib_table->id, false);
recalc:
ret = statistics_init(ib_table, is_analyze);
} else { /* This is e.g. 'SHOW INDEXES' */
ret = statistics_init(ib_table, is_analyze); switch (ret) { case DB_SUCCESS: case DB_READ_ONLY: break; default: goto error; case DB_STATS_DO_NOT_EXIST: if (!ib_table
->stats_is_auto_recalc()) { break;
}
if (opt_bootstrap) { break;
} #ifdef WITH_WSREP if (wsrep_thd_skip_locking(
m_user_thd)) { break;
} #endif
is_analyze = true; goto recalc;
}
}
} else {
ret = dict_stats_update_transient(m_prebuilt->trx, ib_table); if (ret != DB_SUCCESS) {
error:
m_prebuilt->trx->op_info = "";
DBUG_RETURN(HA_ERR_GENERIC);
}
}
m_prebuilt->trx->op_info = "returning various info to MariaDB";
} else {
stats_fetch:
statistics_init(ib_table, false);
}
/* TheMySQLoptimizerseemstoassumeinaleftjointhatn_rows isanaccurateestimateifitiszero.Ofcourse,itisnot, sincewedonothaveanylocksontherowsyetatthisphase. SinceSHOWTABLESTATUSseemstocallthisfunctionwiththe HA_STATUS_TIMEflagset,whiletheleftjoinoptimizerdoesnot setthatflag,weaddonetoazerovalueiftheflagisnot set.ThatwaySHOWTABLESTATUSwillshowthebestestimate,
while the optimizer never sees the table empty. */
/* Fix bug#40386: Not flushing query cache after truncate. n_rowscannotbe0unlessthetableisempty,setto1 instead.Theoriginalproblemofbug#29507isactually
fixed in the server code. */ if (thd_sql_command(m_user_thd) == SQLCOM_TRUNCATE) {
n_rows = 1;
/* We need to reset the m_prebuilt value too, otherwise checksforvaluesgreaterthanthelastvaluewritten tothetablewillfailandtheautoinccounterwill notbeupdated.Thiswillforcewrite_row()into
attempting an update of the table's AUTOINC counter. */
if (flag & HA_STATUS_CONST) { /* Verify the number of index in InnoDB and MySQL matchesup.Ifm_prebuilt->clust_index_was_generated
holds, InnoDB defines GEN_CLUST_INDEX internally */
ulint num_innodb_index = UT_LIST_GET_LEN(ib_table->indexes)
- m_prebuilt->clust_index_was_generated; if (table->s->keys < num_innodb_index) { /* If there are too many indexes defined insideInnoDB,ignorethosethatarebeing created,becauseMySQLwillonlyconsider
the fully built indexes here. */
for (const dict_index_t* index
= UT_LIST_GET_FIRST(ib_table->indexes);
index != NULL;
index = UT_LIST_GET_NEXT(indexes, index)) {
/* First, online index creation is completedinsideInnoDB,andthen MySQLattemptstoupgradethe meta-datalocksothatitcanrebuild the.frmfile.Ifwegethereinthat timeframe,dict_index_is_online_ddl() wouldnotholdandtheindexwould
still not be included in TABLE_SHARE. */ if (!index->is_committed()) {
num_innodb_index--;
}
}
/*********************************************************************//**
Returns statistics information of the table to the MySQL interpreter,
in various fields of the handle object.
@return HA_ERR_* error code or0 */
int
ha_innobase::info( /*==============*/
uint flag) /*!< in: what information is requested */
{ return(info_low(flag, false/* not ANALYZE */));
}
/* Updatesindexcardinalitiesofthetable,basedonrandomdivesinto eachindextree.ThisdoesNOTcalculateexactstatisticsonthetable.
@return HA_ADMIN_* error code or HA_ADMIN_OK */
int
ha_innobase::analyze(THD*, HA_CHECK_OPT*)
{ /* Simply call info_low() with all the flags
and request recalculation of the statistics */ int ret = info_low(
HA_STATUS_TIME | HA_STATUS_CONST | HA_STATUS_VARIABLE, true/* this is ANALYZE */);
if (ret != 0) { return(HA_ADMIN_FAILED);
}
return(HA_ADMIN_OK);
}
/**********************************************************************//** This is mapped to "ALTER TABLE tablename ENGINE=InnoDB", which rebuilds
the table in MySQL. */
/* FTS-FIXME: Since MySQL doesn't support engine-specific commands, wehavetohijacksomeexistingcommandinordertobeabletotest thenewadmincommandsaddedinInnoDB'sFTSsupport.Fornow,we useMySQL'sOPTIMIZEcommand,normallymappedtoALTERTABLEin InnoDB(soitrecreatesthetableanew),andmapittoOPTIMIZE.
ThisworksOKotherwise,butMySQLlockstheentiretableduring
calls to OPTIMIZE, which is undesirable. */ bool try_alter = true;
if (innodb_optimize_fulltext_only) { if (m_prebuilt->table->fts && m_prebuilt->table->fts->cache
&& m_prebuilt->table->space) {
fts_sync_table(m_prebuilt->table, true, thd);
fts_optimize_table(m_prebuilt->table, thd);
}
try_alter = false;
}
/** Does the following validation check for the sequence table 1)CheckwhethertheInnoDBtablehasno_rollbackflags 2)Shouldhaveonlyoneprimaryindex 3)Rootindexpagemustbeleafpage 4)Thereshouldbeonlyonerecordinleafpage 5)Thereshouldn'tbedeletemarkedrecordinleafpage 6)DB_TRX_ID,DB_ROLL_PTRintherecordshouldbe0and1U<<55 @paramprebuilttransactionandtable @retvaltrueifvalid
@retval false if validation fails */ staticbool innobase_sequence_table_check(row_prebuilt_t *prebuilt)
{ const dict_table_t *const table= prebuilt->table;
fil_space_t *space= table->space;
dict_index_t *clust_index= dict_table_get_first_index(table);
mtr_t mtr{prebuilt->trx}; bool corruption= false; const rec_t *rec= nullptr;
buf_block_t *root_block= nullptr;
THD *const thd= mtr.trx->mysql_thd;
if (UT_LIST_GET_LEN(table->indexes) != 1)
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, "InnoDB: Sequence table %s does have more than one " "indexes.", table->name.m_name);
corruption= true; goto func_exit;
}
if (!clust_index->is_gen_clust())
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, "InnoDB: Sequence table %s does not have generated " "clustered index.", table->name.m_name);
corruption= true; goto func_exit;
}
if (!page_is_leaf(root_block->page.frame))
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, "InnoDB: Non leaf page exists for sequence table %s.",
table->name.m_name);
corruption= true; goto err_exit;
}
if (page_get_n_recs(root_block->page.frame) != 1)
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, "InnoDB: Should have only one record in sequence " "table %s. But it has %u records.", table->name.m_name,
page_get_n_recs(root_block->page.frame));
corruption= true; goto err_exit;
}
/*******************************************************************//**
Tries to check that an InnoDB table is not corrupted. If corruption is
noticed, prints to stderr information about it. In case of corruption
may also assert a failure and crash the server.
@return HA_ADMIN_CORRUPT or HA_ADMIN_OK */
if (handler_flags) { /* The file was already checked and fixed as part of open */
print_check_msg(thd, table->s->db.str, table->s->table_name.str, "check", "note",
(opt_readonly || high_level_read_only
|| recv_sys.rpo
|| !(check_opt->sql_flags & TT_FOR_UPGRADE))
? "Auto_increment will be" " checked on each open until" " CHECK TABLE FOR UPGRADE is executed" " when the server is not in a read-only state"
: "Auto_increment checked and" " .frm file version updated", 1); if (check_opt->sql_flags & TT_FOR_UPGRADE) { /* Nootherissuesfound(ashandler_flagswasonly setifthereasnototherproblemswiththetable thanauto_increment).
*/
DBUG_RETURN(HA_ADMIN_OK);
}
}
if (m_prebuilt->mysql_template == NULL) { /* Build the template; we will use a dummy template
in index scans done in checking */
build_template(true);
}
if (!m_prebuilt->table->space) {
ib_senderrf(
thd,
IB_LOG_LEVEL_ERROR,
ER_TABLESPACE_DISCARDED,
table->s->table_name.str);
/* We must run the index record counts at an isolation level >=READCOMMITTED,becauseadirtyreadcanseeawrongnumber ofrecordsinsomeindex;toplaysafe,wenormallyuse
REPEATABLE READ here */
m_prebuilt->trx->isolation_level = high_level_read_only
&& !m_prebuilt->table->is_temporary()
? TRX_ISO_READ_UNCOMMITTED
: TRX_ISO_REPEATABLE_READ;
for (dict_index_t* index
= dict_table_get_first_index(m_prebuilt->table);
index;
index = dict_table_get_next_index(index)) { /* If this is an index being created or dropped, skip */ if (!index->is_committed()) { continue;
} if (index->type & DICT_FTS) { /* We do not check any FULLTEXT INDEX. */ continue;
}
if (btr_validate_index(index, m_prebuilt->trx)
!= DB_SUCCESS) {
is_ok = false;
push_warning_printf(
thd,
Sql_condition::WARN_LEVEL_WARN,
ER_NOT_KEYFILE, "InnoDB: The B-tree of" " index %s is corrupted.",
index->name()); continue;
}
}
/* Instead of invoking change_active_index(), set up adummytemplatefornon-lockingreads,disabling
access to the clustered index. */
m_prebuilt->index = index;
/* Scan this index. */ if (index->is_spatial()) {
ret = row_count_rtree_recs(m_prebuilt, &n_rows);
} elseif (index->type & DICT_FTS) {
ret = DB_SUCCESS;
} else {
ret = row_check_index(m_prebuilt, &n_rows);
}
DBUG_EXECUTE_IF( "dict_set_index_corrupted", if (!index->is_primary()) {
ret = DB_CORRUPTION;
});
if (ret == DB_INTERRUPTED || thd_killed(thd)) { /* Do not report error since this could happen
during shutdown */ break;
}
if (ret == DB_SUCCESS
&& m_prebuilt->autoinc_error != DB_MISSING_HISTORY) { /* See if any non-fatal errors were reported. */
ret = m_prebuilt->autoinc_error;
}
if (ret != DB_SUCCESS) { /* Assume some kind of corruption. */
push_warning_printf(
thd, Sql_condition::WARN_LEVEL_WARN,
ER_NOT_KEYFILE, "InnoDB: The B-tree of" " index %s is corrupted.",
index->name());
is_ok = false;
dict_set_corrupted(m_prebuilt->trx, index, "CHECK TABLE-check index");
}
/* Restore the original isolation level */
m_prebuilt->trx->isolation_level = old_isolation_level & 3; #ifdef BTR_CUR_HASH_ADAPT # ifdefined UNIV_AHI_DEBUG || defined UNIV_DEBUG /* We validate the whole adaptive hash index for all tables
at every CHECK TABLE only when QUICK flag is not present. */
if (!(check_opt->flags & T_QUICK)
&& !btr_search_validate(m_prebuilt->trx->mysql_thd)) {
push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
ER_NOT_KEYFILE, "InnoDB: The adaptive hash index is corrupted.");
is_ok = false;
} # endif /* defined UNIV_AHI_DEBUG || defined UNIV_DEBUG */ #endif/* BTR_CUR_HASH_ADAPT */
func_exit:
m_prebuilt->trx->op_info = "";
/** CheckifthereisaproblemwiththeInnoDBtable. @paramcheck_optcheckoptions @retvalHA_ADMIN_OKifTableisok @retvalHA_ADMIN_NEEDS_ALTERUsershouldrunALTERTABLEFORUPGRADE @retvalHA_ADMIN_NEEDS_CHECKUsershouldrunCHECKTABLEFORUPGRADE
@retval HA_ADMIN_FAILED if InnoDB is in read-only mode */ int ha_innobase::check_for_upgrade(HA_CHECK_OPT *check_opt)
{ /* Checkifthereisapossibilitythattheautoincrementvalue storedinPAGE_ROOT_AUTO_INCcouldbecorrupt.
*/ if (table->s->mysql_version >= 100210); elseif (const Field *auto_increment= table->found_next_number_field)
{
uint col_no= innodb_col_no(auto_increment); const dict_col_t *autoinc_col=
dict_table_get_nth_col(m_prebuilt->table, col_no); if (m_prebuilt->table->get_index(*autoinc_col))
{
check_opt->handler_flags= 1; // Prevent ha_check() from updating frm version if InnoDB (but not the server) is in a read-only state return ((high_level_read_only || recv_sys.rpo) && !opt_readonly)
? HA_ADMIN_FAILED : HA_ADMIN_NEEDS_CHECK;
}
} return HA_ADMIN_OK;
}
/*******************************************************************//**
Gets the foreign key create info for a table stored in InnoDB.
@return own: character string in the form which can be inserted to the
CREATE TABLE statement, MUST be freed with
ha_innobase::free_foreign_key_create_info */
do {
ptr = foreign->foreign_col_names[i];
name = thd_make_lex_string(thd, name, ptr,
strlen(ptr), 1);
f_key_info.foreign_fields.push_back(name);
if (dict_index_t* fidx = foreign->foreign_index) { if (fidx->fields[i].col->is_nullable()) {
f_key_info.set_nullable(thd, false, i,
foreign->n_fields);
}
}
ptr = foreign->referenced_col_names[i];
name = thd_make_lex_string(thd, name, ptr,
strlen(ptr), 1);
f_key_info.referenced_fields.push_back(name);
if (dict_index_t* ref_idx = foreign->referenced_index) { if (ref_idx->fields[i].col->is_nullable()) {
f_key_info.set_nullable(thd, true, i,
foreign->n_fields);
}
}
/*******************************************************************//**
Gets the list of foreign keys in this table.
@return always 0, that is, always succeeds */
int
ha_innobase::get_foreign_key_list( /*==============================*/
THD* thd, /*!< in: user thread handle */
List<FOREIGN_KEY_INFO>* f_key_list) /*!< out: foreign key list */
{
update_thd(ha_thd());
m_prebuilt->trx->op_info = "getting list of foreign keys";
dict_sys.lock(SRW_LOCK_CALL);
for (dict_foreign_set::iterator it
= m_prebuilt->table->foreign_set.begin();
it != m_prebuilt->table->foreign_set.end();
++it) {
if (pf_key_info != NULL) {
f_key_list->push_back(pf_key_info);
}
}
dict_sys.unlock();
m_prebuilt->trx->op_info = "";
return(0);
}
/*******************************************************************//**
Gets the set of foreign keys where this table is the referenced table.
@return always 0, that is, always succeeds */
int
ha_innobase::get_parent_foreign_key_list( /*=====================================*/
THD* thd, /*!< in: user thread handle */
List<FOREIGN_KEY_INFO>* f_key_list) /*!< out: foreign key list */
{
update_thd(ha_thd());
m_prebuilt->trx->op_info = "getting list of referencing foreign keys";
dict_sys.freeze(SRW_LOCK_CALL);
for (dict_foreign_set::iterator it
= m_prebuilt->table->referenced_set.begin();
it != m_prebuilt->table->referenced_set.end();
++it) {
if (pf_key_info != NULL) {
f_key_list->push_back(pf_key_info);
}
}
dict_sys.unfreeze();
m_prebuilt->trx->op_info = "";
return(0);
}
/** Table list item structure is used to store only the table andname.Itisusedbyget_cascade_foreign_key_table_listtostore
the intermediate result for fetching the table set. */ struct table_list_item { /** InnoDB table object */ const dict_table_t* table; /** Table name */ constchar* name;
};
/** @return whether ALTER TABLE may change the storage engine of the table */ bool ha_innobase::can_switch_engines()
{
DBUG_ENTER("ha_innobase::can_switch_engines");
update_thd();
DBUG_RETURN(m_prebuilt->table->foreign_set.empty() &&
m_prebuilt->table->referenced_set.empty());
}
/** Checks if a table is referenced by a foreign key. The MySQL manual states thataREPLACEiseitherequivalenttoanINSERT,orDELETE(s)+INSERT.Onlya deleteisthenallowedinternallytoresolveaduplicatekeyconflictin REPLACE,notanupdate.
@return whether the table is referenced by a FOREIGN KEY */ bool ha_innobase::referenced_by_foreign_key() const noexcept
{
dict_sys.freeze(SRW_LOCK_CALL); constbool empty= m_prebuilt->table->referenced_set.empty();
dict_sys.unfreeze(); return !empty;
}
/*******************************************************************//**
Tells something additional to the handler about how to do things.
@return0or error number */
int
ha_innobase::extra( /*===============*/ enum ha_extra_function operation) /*!< in: HA_EXTRA_FLUSH or some other flag */
{ /* Warning: since it is not sure that MariaDB calls external_lock()
before calling this function, m_prebuilt->trx can be obsolete! */
trx_t* trx;
THD* thd = ha_thd();
switch (operation) { case HA_EXTRA_FLUSH:
(void)check_trx_exists(thd); if (m_prebuilt->blob_heap) {
row_mysql_prebuilt_free_blob_heap(m_prebuilt);
} break; case HA_EXTRA_RESET_STATE:
trx = check_trx_exists(thd);
reset_template();
trx->duplicates = 0;
stmt_boundary:
trx->bulk_insert_apply();
trx->end_bulk_insert(*m_prebuilt->table);
trx->clear_dml_bulk(); break; case HA_EXTRA_NO_KEYREAD:
(void)check_trx_exists(thd);
m_prebuilt->read_just_key = 0; break; case HA_EXTRA_KEYREAD:
(void)check_trx_exists(thd);
m_prebuilt->read_just_key = 1; break; case HA_EXTRA_KEYREAD_PRESERVE_FIELDS:
(void)check_trx_exists(thd);
m_prebuilt->keep_other_fields_on_keyread = 1; break; case HA_EXTRA_INSERT_WITH_UPDATE:
trx = check_trx_exists(thd);
trx->duplicates |= TRX_DUP_IGNORE; goto stmt_boundary; case HA_EXTRA_NO_IGNORE_DUP_KEY:
trx = check_trx_exists(thd);
trx->duplicates &= TRX_DUP_REPLACE; if (trx->is_bulk_insert()) { /* Allow a subsequent INSERT into an empty table
if !unique_checks && !foreign_key_checks. */ if (dberr_t err = trx->bulk_insert_apply()) { return convert_error_code_to_mysql(
err, 0, trx->mysql_thd);
} break;
} goto stmt_boundary; case HA_EXTRA_WRITE_CAN_REPLACE:
trx = check_trx_exists(thd);
trx->duplicates |= TRX_DUP_REPLACE; goto stmt_boundary; case HA_EXTRA_WRITE_CANNOT_REPLACE:
trx = check_trx_exists(thd);
trx->duplicates &= TRX_DUP_IGNORE; if (trx->is_bulk_insert()) { /* Allow a subsequent INSERT into an empty table
if !unique_checks && !foreign_key_checks. */ break;
} goto stmt_boundary; case HA_EXTRA_BEGIN_ALTER_IGNORE_COPY: case HA_EXTRA_BEGIN_COPY:
trx = check_trx_exists(thd);
static_assert(int{HA_EXTRA_BEGIN_ALTER_IGNORE_COPY} == int{HA_EXTRA_BEGIN_COPY} + 1, "");
static_assert(int{dict_table_t::NO_UNDO} + 1 == int{dict_table_t::IGNORE_UNDO}, ""); if (m_prebuilt->table->is_temporary()) {
m_prebuilt->table->skip_alter_undo =
(HA_EXTRA_BEGIN_ALTER_IGNORE_COPY
- operation + dict_table_t::NORMAL_UNDO) & 1; break;
}
if (!m_prebuilt->table->versioned_by_id()) { break;
}
ut_ad(trx == m_prebuilt->trx);
trx_start_if_not_started(trx, true);
trx->mod_tables.emplace( const_cast<dict_table_t*>(m_prebuilt->table), 0)
.first->second.set_versioned(0); break; case HA_EXTRA_END_COPY:
{
trx = check_trx_exists(thd); constunsigned alter_undo = m_prebuilt->table->skip_alter_undo; if (!alter_undo) { /* This could be invoked inside INSERT...SELECT. Wedonotwantanyextralogwrites,because
they could cause a severe performance regression. */ break;
}
if (alter_undo == dict_table_t::IGNORE_UNDO) {
trx->reset_and_truncate_undo();
}
trx->end_bulk_insert(*m_prebuilt->table);
trx->clear_ddl_bulk(); if (!m_prebuilt->table->is_temporary()
&& !high_level_read_only && !recv_sys.rpo) { /* During copy_data_between_tables(), InnoDB only
updates transient statistics. */ if (!m_prebuilt->table->stats_is_persistent()) {
dict_stats_update_if_needed(m_prebuilt->table,
*trx);
} /* The extra log write is necessary for ALTERTABLE...ALGORITHM=COPY,because anormaltransactioncommitwouldbeano-op becausenoundologrecordsweregenerated. Thislogwritewillalsobeunnecessarilyexecuted duringCREATE...SELECT,whichistheothercallerof
handler::extra(HA_EXTRA_ALTER_COPY). */
log_buffer_flush_to_disk();
}
alter_stats_rebuild(m_prebuilt->table, trx, true); break;
} case HA_EXTRA_ABORT_COPY: /* Abort the ALTER COPY operation. For ALTER IGNORE, thispreventsundologsfrombeingaddedtothe purgethread,allowingpropercleanupofthe
temporary undo state. */ if (m_prebuilt->table->skip_alter_undo &&
!m_prebuilt->table->is_temporary()) {
trx = check_trx_exists(ha_thd());
m_prebuilt->table->skip_alter_undo =
dict_table_t::NORMAL_UNDO;
trx->rollback(); return(HA_ERR_ROLLBACK);
} break; default:/* Do nothing */
;
}
return(0);
}
/** SQL layer calls this function (via init_table_full_scan_if_needed()) withHA_EXTRA_FULL_SCANwhenastatementreadsawholetableor indexwithnoWHEREcondition.Wheninnodb_table_lock_on_full_scan isenabled,flagthescanascoveringtheentiretable. row_search_mvcc()takesasingletable-levellock(LOCK_S/LOCK_X) insteadoflockingeachscannedrowindividually.Abounded scan(arg<ULONG_MAX)keepsper-rowlocking,sinceitmaystopearly. Everyotheroperationisforwardedunchangedtoextra(). @paramoperationhinttoacton @paramargoperationargument;forHA_EXTRA_FULL_SCANtherow limit,orULONG_MAXwhenthescanisunbounded
@return 0 on success, otherwise the error code returned by extra() */ int ha_innobase::extra_opt(enum ha_extra_function operation, ulong arg)
{ switch (operation)
{ case HA_EXTRA_FULL_SCAN: if (THDVAR(ha_thd(), table_lock_on_full_scan) &&
!m_prebuilt->skip_locked && arg == ULONG_MAX)
m_prebuilt->full_table_scan = true; return0; default:/* Do nothing */
;
} return extra(operation);
}
/**
MySQL calls this method at the end of each statement */ int
ha_innobase::reset()
{ if (m_prebuilt->blob_heap) {
row_mysql_prebuilt_free_blob_heap(m_prebuilt);
}
reset_template();
m_ds_mrr.dsmrr_close();
/* TODO: This should really be reset in reset_template() but for now
it's safer to do it explicitly here. */
/* This is a statement level counter. */
m_prebuilt->autoinc_last_value = 0;
/******************************************************************//**
MySQL calls this function at the start of each SQL statement inside LOCK
TABLES. Inside LOCK TABLES the ::external_lock method does not work to
mark SQL statement borders. Note also a special case: if a temporary table
is created inside LOCK TABLES, MySQL has not called external_lock() at all
on that table.
MySQL-5.0 also calls this before each statement in an execution of a stored
procedure. To make the execution more deterministic for binlogging, MySQL-5.0
locks all tables involved in a stored procedure with full explicit table
locks (thd_in_lock_tables(thd) holds in store_lock()) before executing the
procedure.
@return0or error code */
int
ha_innobase::start_stmt( /*====================*/
THD* thd, /*!< in: handle to the user thread */
thr_lock_type lock_type)
{
trx_t* trx = m_prebuilt->trx;
DBUG_ENTER("ha_innobase::start_stmt");
update_thd(thd);
ut_ad(m_prebuilt->table != NULL);
trx = m_prebuilt->trx;
switch (trx->state) { default:
DBUG_RETURN(HA_ERR_ROLLBACK); case TRX_STATE_ACTIVE: break; case TRX_STATE_NOT_STARTED:
trx->will_lock = true; break;
}
/* Reset the AUTOINC statement level counter for multi-row INSERTs. */
trx->n_autoinc_rows = 0;
switch (sql_command) { case SQLCOM_INSERT: case SQLCOM_INSERT_SELECT: if (trx->is_bulk_insert()) { /* Allow a subsequent INSERT into an empty table
if !unique_checks && !foreign_key_checks. */
ut_ad(!trx->duplicates); break;
} /* fall through */ default:
trx->bulk_insert_apply();
trx->end_bulk_insert(); if (!trx->bulk_insert) { break;
}
if (m_prebuilt->table->is_temporary()
&& m_mysql_has_locked
&& m_prebuilt->select_lock_type == LOCK_NONE) { switch (sql_command) { case SQLCOM_INSERT: case SQLCOM_UPDATE: case SQLCOM_DELETE: case SQLCOM_REPLACE:
init_table_handle_for_HANDLER();
m_prebuilt->select_lock_type = LOCK_X;
m_prebuilt->stored_select_lock_type = LOCK_X; if (dberr_t error = row_lock_table(m_prebuilt)) {
DBUG_RETURN(convert_error_code_to_mysql(
error, 0, thd));
} break; default: break;
}
}
if (!m_mysql_has_locked) { /* This handle is for a temporary table created inside thissameLOCKTABLES;sinceMySQLdoesNOTcallexternal_lock inthiscase,wemustusex-rowlocksinsideInnoDBtobe
prepared for an update of a row */
/******************************************************************//**
As MySQL will execute an external lock for every new table it uses when it
starts to process an SQL statement (an exception is when MySQL calls
start_stmt for the handle) we can use this function to store the pointer to
the THD in the handle. We will also use this function to communicate
to InnoDB that a new SQL statement has started and that we must store a
savepoint to our transaction handle, so that we are able to roll back
the SQL statement in case of an error.
@return0 */
int
ha_innobase::external_lock( /*=======================*/
THD* thd, /*!< in: handle to the user thread */ int lock_type) /*!< in: lock type */
{
DBUG_ENTER("ha_innobase::external_lock");
DBUG_PRINT("enter",("lock_type: %d", lock_type));
if (table->s->tmp_table == INTERNAL_TMP_TABLE)
trx->check_unique_secondary = true;
constbool not_autocommit = thd_test_options(thd, OPTION_NOT_AUTOCOMMIT
| OPTION_BEGIN); bool not_started = false; switch (trx->state) { default: case TRX_STATE_PREPARED:
ut_ad("invalid state" == 0);
DBUG_RETURN(HA_ERR_WRONG_COMMAND); case TRX_STATE_ABORTED: if (lock_type != F_UNLCK && not_autocommit) {
DBUG_RETURN(HA_ERR_WRONG_COMMAND);
} /* Reset the state if the transaction had been aborted. */
trx->state = TRX_STATE_NOT_STARTED; /* fall through */ case TRX_STATE_NOT_STARTED:
not_started = true; break; case TRX_STATE_ACTIVE: break;
}
/* Statement based binlogging does not work in isolation level READUNCOMMITTEDandREADCOMMITTEDsincethenecessary lockscannotbetaken.Inthiscase,weprintan informativeerrormessageandreturnwithanerror. Note:decide_logging_formatwouldgivethesameerrormessage,
except it cannot give the extra details. */
if (lock_type == F_WRLCK
&& !(table_flags() & HA_BINLOG_STMT_CAPABLE)
&& thd_binlog_format(thd) == BINLOG_FORMAT_STMT
&& thd_binlog_filter_ok(thd)
&& thd_sqlcom_can_generate_row_events(thd)) { bool skip = false; #ifdef WITH_WSREP
skip = trx->is_wsrep() && !wsrep_thd_is_local(thd); #endif/* WITH_WSREP */ /* used by test case */
DBUG_EXECUTE_IF("no_innodb_binlog_errors", skip = true;);
if (!skip) {
my_error(ER_BINLOG_STMT_MODE_AND_ROW_ENGINE, MYF(0), " InnoDB is limited to row-logging when" " transaction isolation level is" " READ COMMITTED or READ UNCOMMITTED.");
DBUG_RETURN(HA_ERR_LOGGING_IMPOSSIBLE);
}
}
constauto sql_command = thd_sql_command(thd);
/* Check for UPDATEs in read-only mode. */ if (recv_sys.rpo) { switch (sql_command) { case SQLCOM_CREATE_TABLE: if (lock_type != F_WRLCK) { break;
} /* fall through */ case SQLCOM_UPDATE: case SQLCOM_INSERT: case SQLCOM_REPLACE: case SQLCOM_DROP_TABLE: case SQLCOM_ALTER_TABLE: case SQLCOM_OPTIMIZE: case SQLCOM_CREATE_INDEX: case SQLCOM_DROP_INDEX: case SQLCOM_CREATE_SEQUENCE: case SQLCOM_DROP_SEQUENCE: case SQLCOM_DELETE:
ib_senderrf(thd, IB_LOG_LEVEL_WARN,
ER_READ_ONLY_MODE);
DBUG_RETURN(HA_ERR_TABLE_READONLY); default: break;
}
}
reset_template(); switch (sql_command) { case SQLCOM_INSERT: case SQLCOM_INSERT_SELECT: if (trx->is_bulk_insert()) { /* Allow a subsequent INSERT into an empty table
if !unique_checks && !foreign_key_checks. */
ut_ad(!trx->duplicates); break;
} /* fall through */ default:
trx->bulk_insert_apply();
trx->end_bulk_insert(); if (!trx->bulk_insert) { break;
}
trx->clear_dml_bulk();
trx->last_stmt_start = trx->undo_no;
}
switch (m_prebuilt->table->quiesce) { case QUIESCE_START: /* Check for FLUSH TABLE t WITH READ LOCK; */ if (!recv_sys.rpo
&& sql_command == SQLCOM_FLUSH
&& lock_type == F_RDLCK) {
if (!m_prebuilt->table->space) {
ib_senderrf(trx->mysql_thd, IB_LOG_LEVEL_ERROR,
ER_TABLESPACE_DISCARDED,
table->s->table_name.str);
DBUG_RETURN(HA_ERR_TABLESPACE_MISSING);
}
row_quiesce_table_start(m_prebuilt->table, trx);
/* Use the transaction instance to track UNLOCK TABLES.ItcanbedoneviaSTARTTRANSACTION;too
implicitly. */
++trx->flush_tables;
} break;
case QUIESCE_COMPLETE: /* Check for UNLOCK TABLES; implicit or explicit
or trx interruption. */ if (trx->flush_tables > 0
&& (lock_type == F_UNLCK || trx_is_interrupted(trx))) {
switch (lock_type) { case F_UNLCK:
DEBUG_SYNC_C("ha_innobase_end_statement");
m_mysql_has_locked = false;
ut_a(trx->n_mysql_tables_in_use);
if (--trx->n_mysql_tables_in_use) { break;
}
/* If the lock count drops to zero we know that the
current SQL statement has ended */
trx->mysql_n_tables_locked = 0;
m_prebuilt->used_in_HANDLER = FALSE;
if (!not_autocommit) { if (!not_started) {
innobase_commit(thd, TRUE);
}
} elseif (trx->isolation_level <= TRX_ISO_READ_COMMITTED) {
trx->read_view.close();
} break; case F_WRLCK: /* If this is a SELECT, then it is in UPDATE TABLE ...
or SELECT ... FOR UPDATE */
m_prebuilt->select_lock_type = LOCK_X;
m_prebuilt->stored_select_lock_type = LOCK_X; goto set_lock; case F_RDLCK: /* Ensure that trx->lock.trx_locks is empty for read-only
autocommit transactions */
ut_ad(not_autocommit || trx->n_mysql_tables_in_use
|| UT_LIST_GET_LEN(trx->lock.trx_locks) == 0);
set_lock:
*trx->detailed_error = 0;
/* To get serializable execution, we let InnoDB conceptuallyadd'LOCKINSHAREMODE'toallSELECTs whichotherwisewouldhavebeenconsistentreads.An exceptionisconsistentreadsintheAUTOCOMMIT=1mode: weknowthattheyareread-onlytransactions,andthey canbeserializedalsoifperformedasconsistent
reads. */
/* Starting from 4.1.9, no InnoDB table lock is taken in LOCK TABLESifAUTOCOMMIT=1.Itdoesnotmakemuchsensetoacquire anInnoDBtablelockifitisreleasedimmediatelyattheend ofLOCKTABLES,andInnoDB'stablelocksinthatcasecause VERYeasilydeadlocks.
/************************************************************************//**
Here we export InnoDB status variables to MySQL. */ static void
innodb_export_status() /*==================*/
{ if (srv_was_started) {
srv_export_innodb_status();
}
}
/************************************************************************//**
Implements the SHOW ENGINE INNODB STATUS command. Sends the output of the
InnoDB Monitor to the client.
@return0 on success */ static int
innodb_show_status( /*===============*/
handlerton* hton, /*!< in: the innodb handlerton */
THD* thd, /*!< in: the MySQL query thread of the caller */
stat_print_fn* stat_print)
{ staticconstchar truncated_msg[] = "... truncated...\n"; constlong MAX_STATUS_SIZE = 1048576;
ulint trx_list_start = ULINT_UNDEFINED;
ulint trx_list_end = ULINT_UNDEFINED; bool ret_val;
/************************************************************************//** Return0 on success and non-zero on failure. Note: the boolreturn type
seems to be abused here, should be an int. */ static bool
innobase_show_status( /*=================*/
handlerton* hton, /*!< in: the innodb handlerton */
THD* thd, /*!< in: the MySQL query thread
of the caller */
stat_print_fn* stat_print, enum ha_stat_type stat_type)
{
DBUG_ASSERT(hton == innodb_hton_ptr);
switch (stat_type) { case HA_ENGINE_STATUS: /* Non-zero return value means there was an error. */ return(innodb_show_status(hton, thd, stat_print) != 0);
case HA_ENGINE_MUTEX: case HA_ENGINE_LOGS: /* Not handled */ break;
}
/* Success */ return(false);
}
/*********************************************************************//**
Returns number of THR_LOCK locks used for one instance of InnoDB table.
InnoDB no longer relies on THR_LOCK locks so 0 value is returned.
Instead of THR_LOCK locks InnoDB relies on combination of metadata locks
(e.g. for LOCK TABLES and DDL) and its own locking subsystem.
Note that even though this method returns 0, SQL-layer still calls
::store_lock(), ::start_stmt() and ::external_lock() methods for InnoDB
tables. */
/*****************************************************************//**
Supposed to convert a MySQL table lock stored in the 'lock' field of the
handle to a proper type before storing pointer to the lock into an array
of pointers.
In practice, since InnoDB no longer relies on THR_LOCK locks and its
lock_count() method returns 0 it just informs storage engine about type
of THR_LOCK which SQL-layer would have acquired forthis specific statement
on this specific table.
MySQL also calls thisif it wants to reset some table locks to a not-locked
state during the processing of an SQL query. An example is that during a
SELECT the read lock is released early on the 'const' tables where we only
fetch one row. MySQL does not call this when it releases all locks at the
end of an SQL statement.
@return pointer to the current element in the 'to' array. */
THR_LOCK_DATA**
ha_innobase::store_lock( /*====================*/
THD* thd, /*!< in: user thread handle */
THR_LOCK_DATA** to, /*!< in: pointer to the current elementinanarrayofpointers tolockstructs;
only used as return value */
thr_lock_type lock_type) /*!< in: lock type to store in 'lock';thismayalsobe
TL_IGNORE */
{
ut_ad(!srv_read_only_mode || recv_sys.rpo);
/* Note that trx in this function is NOT necessarily m_prebuilt->trx becausewecallupdate_thd()later,in::external_lock()!Failureto
understand this caused a serious memory corruption bug in 5.1.11. */
trx_t* trx = check_trx_exists(thd);
/* NOTE: MySQL can call this function with lock 'type' TL_IGNORE! BecarefultoignoreTL_IGNOREifwearegoingtodosomethingwith
only 'real' locks! */
/* If no table handle is open, we need to set the isolation level
of the transaction. */
if (lock_type != TL_IGNORE
&& trx->n_mysql_tables_in_use == 0) { switch ((trx->isolation_level
= innodb_isolation_level(thd) & 3)) { case ISO_REPEATABLE_READ: break; case ISO_READ_COMMITTED: case ISO_READ_UNCOMMITTED: /* At low transaction isolation levels we let
each consistent read set its own snapshot */
trx->read_view.close(); break; case ISO_SERIALIZABLE: auto trx_state = trx->state; if (trx_state != TRX_STATE_NOT_STARTED) {
ut_ad(trx_state == TRX_STATE_ACTIVE);
} elseif (trx->snapshot_isolation) {
trx->will_lock = true;
trx_start_if_not_started(trx, false);
trx->read_view.open(trx);
}
}
}
/* Note: This call can fail, but there is no way to return theerrortothecaller.Wesimplyignoreitfornowhere andpushtheerrorcodetothecallerwheretheerroris
detected in the function. */
/* Check for DROP TABLE */
} elseif (sql_command == SQLCOM_DROP_TABLE ||
sql_command == SQLCOM_DROP_SEQUENCE) {
/* MySQL calls this function in DROP TABLE though this table handlemaybelongtoanotherthdthatisrunningaquery.Let
us in that case skip any changes to the m_prebuilt struct. */
/* The OR cases above are in this order: 1)MySQLisdoingLOCKTABLES...READLOCAL,or 2)thisisaSELECT...INSHAREMODE,or 3)thisisaSELECT...INSHAREMODESKIPLOCKED,or 4)wearedoingacomplexSQLstatementlike INSERTINTO...SELECT...andthelogicallogging(MySQL binlog)requirestheuseofalockingread,or MySQLisdoingLOCKTABLES...READ. 5)weletInnoDBdolockingreadsforallSQLstatementsthat maymodifydata;notethatselect_lock_typeinthis casemaygetstrengthenedin::external_lock()toLOCK_X. NotethatweMUSTusealockingreadinalldatamodifying SQLstatements,becauseotherwisetheexecutionwouldnotbe serializable,andalsotheresultsfromtheupdatecouldbe unexpectedifanobsoleteconsistentreadviewwouldbe
used. */
/* If the transaction isolation level is READUNCOMMITTEDorREADCOMMITTEDandweareexecuting INSERTINTO...SELECTorREPLACEINTO...SELECT orUPDATE...=(SELECT...)orCREATE... SELECT...withoutFORUPDATEorINSHARE MODEinselect,thenweuseconsistentread
for select. */
/*********************************************************************//**
Read the next autoinc value. Acquire the relevant locks before reading
the AUTOINC value. If SUCCESS then the table AUTOINC mutex will be locked
on returnand all relevant locks acquired.
@return DB_SUCCESS or error code */
/* Determine the first value of the interval */
*value = dict_table_autoinc_read(m_prebuilt->table);
/* It should have been initialized during open. */ if (*value == 0) {
m_prebuilt->autoinc_error = DB_UNSUPPORTED;
m_prebuilt->table->autoinc_mutex.wr_unlock();
}
}
return(m_prebuilt->autoinc_error);
}
/*******************************************************************//** This function reads the global auto-inc counter. It doesn't use the
AUTOINC lock even if the lock mode is set to TRADITIONAL.
@return the autoinc value */
if (auto_inc == 0) {
ib::info() << "AUTOINC next value generation is disabled for" " '" << innodb_table->name << "'";
}
innodb_table->autoinc_mutex.wr_unlock();
return(auto_inc);
}
/*********************************************************************//**
Returns the value of the auto-inc counter in *first_value and ~0 on failure. */
/* Prepare m_prebuilt->trx in the table handle */
update_thd(ha_thd());
error = innobase_get_autoinc(&autoinc);
if (error != DB_SUCCESS) {
*first_value = (~(ulonglong) 0); /* This is an error case. We do the error handling by calling theerrorcodeconversionfunction.Specifically,weneedto callthd_mark_transaction_to_rollback()toinformsqlthatwe haverolledbackinnodbtransactionafteradeadlockerror.We
ignore the returned mysql error code here. */
std::ignore = convert_error_code_to_mysql(
error, m_prebuilt->table->flags, m_user_thd); return;
}
/* This is a hack, since nb_desired_values seems to be accurate only forthefirstcalltoget_auto_increment()formulti-rowINSERTand meaninglessforotherstatementse.g,LOADetc.Subsequentcallsto thismethodforthesamestatementresultsindifferentvalueswhich don'tmakesense.Thereforewestorethevaluethefirsttimeweare calledandcountdownfromthatasrowsarewritten(seewrite_row()).
*/
trx = m_prebuilt->trx;
/* Note: We can't rely on *first_value since some MySQL engines, inparticularthepartitionengine,don'tinitializeitto0when invokingthismethod.Sowearenotsureifit'sguaranteedto
be 0 or not. */
/* We need the upper limit of the col type to check for
whether we update the table autoinc counter or not. */
ulonglong col_max_value =
table->next_number_field->get_max_int_value();
/** The following logic is needed to avoid duplicate key error forautoincrementcolumn.
/* If autoinc exceeds the col_max_value then reset tooldautoincvalue.Becauseincaseofnon-strict
sql mode, boundary value is not considered as error. */
if (autoinc >= col_max_value) {
autoinc = prev_auto_inc;
}
ut_ad(autoinc > 0);
}
/* Called for the first time ? */ if (trx->n_autoinc_rows == 0) {
trx->n_autoinc_rows = (ulint) nb_desired_values;
/* It's possible for nb_desired_values to be 0:
e.g., INSERT INTO T1(C) SELECT C FROM T2; */ if (nb_desired_values == 0) {
trx->n_autoinc_rows = 1;
}
set_if_bigger(*first_value, autoinc); /* Not in the middle of a mult-row INSERT. */
} elseif (m_prebuilt->autoinc_last_value == 0) {
set_if_bigger(*first_value, autoinc);
}
if (*first_value > col_max_value) { /* Out of range number. Let handler::update_auto_increment()
take care of this */
m_prebuilt->autoinc_last_value = 0;
m_prebuilt->table->autoinc_mutex.wr_unlock();
*nb_reserved_values= 0; return;
}
*nb_reserved_values = trx->n_autoinc_rows;
/* With old style AUTOINC locking we only update the table's
AUTOINC counter after attempting to insert the row. */ if (innobase_autoinc_lock_mode != AUTOINC_OLD_STYLE_LOCKING) {
ulonglong current;
ulonglong next_value;
current = *first_value;
/* Compute the last value in the interval */
next_value = innobase_next_autoinc(
current, *nb_reserved_values, increment, offset,
col_max_value);
m_prebuilt->autoinc_last_value = next_value;
if (m_prebuilt->autoinc_last_value < *first_value) {
*first_value = (~(ulonglong) 0);
} else { /* Update the table autoinc variable */
dict_table_autoinc_update_if_greater(
m_prebuilt->table,
m_prebuilt->autoinc_last_value);
}
} else { /* This will force write_row() into attempting an update
of the table's AUTOINC counter. */
m_prebuilt->autoinc_last_value = 0;
}
/* The increment to be used to increase the AUTOINC value, we use thisinwrite_row()andupdate_row()toincreasetheautoinccounter forcolumnsthatarefilledbytheuser.Weneedtheoffsetand
the increment. */
m_prebuilt->autoinc_offset = offset;
m_prebuilt->autoinc_increment = increment;
m_prebuilt->table->autoinc_mutex.wr_unlock();
}
/*******************************************************************//**
See comment in handler.cc */
if (error == HA_ERR_DECRYPTION_FAILED) { constchar *msg = "Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.";
buf->copy(msg, (uint)strlen(msg), system_charset_info);
} else {
buf->copy(trx->detailed_error, (uint) strlen(trx->detailed_error),
system_charset_info);
}
return(FALSE);
}
/** Retrieves the names of the table and the key for which there was a duplicateentryinthecaseofHA_ERR_FOREIGN_DUPLICATE_KEY.
if (err_index == NULL) { return(false);
} /* else */
/* copy table name (and convert from filename-safe encoding to
system_charset_info) */ char* p = strchr(err_index->table->name.m_name, '/');
/* strip ".../" prefix if any */ if (p != NULL) {
p++;
} else {
p = err_index->table->name.m_name;
}
size_t len;
len = filename_to_tablename(p, child_table_name, child_table_name_len);
child_table_name[len] = '\0';
/* copy index name */
snprintf(child_key_name, child_key_name_len, "%s",
err_index->name());
return(true);
}
/*******************************************************************//**
Compares two 'refs'. A 'ref' is the (internal) primary key value of the row. If there is no explicitly declared non-null unique key or a primary key, then
InnoDB internally uses the row id as the primary key.
@return < 0if ref1 < ref2, 0if equal, else > 0 */
int
ha_innobase::cmp_ref( /*=================*/ const uchar* ref1, /*!< in: an (internal) primary key value in the
MySQL key value format */ const uchar* ref2) /*!< in: an (internal) primary key value in the
MySQL key value format */
{
enum_field_types mysql_type;
Field* field;
KEY_PART_INFO* key_part;
KEY_PART_INFO* key_part_end;
uint len1;
uint len2; int result;
if (m_prebuilt->clust_index_was_generated) { /* The 'ref' is an InnoDB row id */
return(memcmp(ref1, ref2, DATA_ROW_ID_LEN));
}
/* Do a type-aware comparison of primary key fields. PK fields
are always NOT NULL, so no checks for NULL are performed. */
/*******************************************************************//**
Ask InnoDB if a query to a table can be cached.
@returnTRUEif query caching of the table is permitted */
my_bool
ha_innobase::register_query_cache_table( /*====================================*/
THD* thd, /*!< in: user thread handle */ constchar* table_key, /*!< in: normalized path to the
table */
uint key_length, /*!< in: length of the normalized
path to the table */
qc_engine_callback*
call_back, /*!< out: pointer to function for checkingifquerycaching
is permitted */
ulonglong *engine_data) /*!< in/out: data to call_back */
{
*engine_data = 0;
*call_back = innobase_query_caching_of_table_permitted;
/******************************************************************//** This function is used to find the storage length in bytes of the first n
characters for prefix indexes using a multibyte character set. The function
finds charset information and returns length of prefix_len characters in the
index field in bytes.
@return number of bytes occupied by the first n characters */
ulint
innobase_get_at_most_n_mbchars( /*===========================*/
ulint charset_id, /*!< in: character set id */
ulint prefix_len, /*!< in: prefix length in bytes of the index (thishastobedividedbymbmaxlentogetthe
number of CHARACTERS n in the prefix) */
ulint data_len, /*!< in: length of the string in bytes */ constchar* str) /*!< in: character string */
{
ulint char_length; /*!< character length in bytes */
ulint n_chars; /*!< number of characters in prefix */
CHARSET_INFO* charset; /*!< charset used in the field */
/* Calculate how many characters at most the prefix index contains */
n_chars = prefix_len / charset->mbmaxlen;
/* If the charset is multi-byte, then we must find the length of the firstatmostncharsinthestring.Ifthestringcontainsless charactersthann,thenwereturnthelengthtotheendofthelast
character. */
if (charset->mbmaxlen > 1) {
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.1.672Bemerkung:
(vorverarbeitet am 2026-10-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.