diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:00:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:00:34 +0000 |
commit | 3f619478f796eddbba6e39502fe941b285dd97b1 (patch) | |
tree | e2c7b5777f728320e5b5542b6213fd3591ba51e2 /sql/sql_admin.cc | |
parent | Initial commit. (diff) | |
download | mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.tar.xz mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.zip |
Adding upstream version 1:10.11.6.upstream/1%10.11.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sql/sql_admin.cc')
-rw-r--r-- | sql/sql_admin.cc | 1656 |
1 files changed, 1656 insertions, 0 deletions
diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc new file mode 100644 index 00000000..fcbd8a55 --- /dev/null +++ b/sql/sql_admin.cc @@ -0,0 +1,1656 @@ +/* Copyright (c) 2010, 2015, Oracle and/or its affiliates. + Copyright (c) 2011, 2021, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include "mariadb.h" +#include "sql_class.h" // THD +#include "keycaches.h" // get_key_cache +#include "sql_base.h" // Open_table_context +#include "lock.h" // MYSQL_OPEN_* +#include "sql_handler.h" // mysql_ha_rm_tables +#include "partition_element.h" // PART_ADMIN +#include "sql_partition.h" // set_part_state +#include "transaction.h" // trans_rollback_stmt +#include "sql_view.h" // view_checksum +#include "sql_table.h" // mysql_recreate_table +#include "debug_sync.h" // DEBUG_SYNC +#include "sp.h" // Sroutine_hash_entry +#include "sql_parse.h" // check_table_access +#include "strfunc.h" +#include "sql_admin.h" +#include "sql_statistics.h" +#include "wsrep_mysqld.h" +#ifdef WITH_WSREP +#include "wsrep_trans_observer.h" +#endif + +const LEX_CSTRING msg_status= {STRING_WITH_LEN("status")}; +const LEX_CSTRING msg_repair= { STRING_WITH_LEN("repair") }; +const LEX_CSTRING msg_assign_to_keycache= +{ STRING_WITH_LEN("assign_to_keycache") }; +const LEX_CSTRING msg_analyze= { STRING_WITH_LEN("analyze") }; +const LEX_CSTRING msg_check= { STRING_WITH_LEN("check") }; +const LEX_CSTRING msg_preload_keys= { STRING_WITH_LEN("preload_keys") }; +const LEX_CSTRING msg_optimize= { STRING_WITH_LEN("optimize") }; + +/* Prepare, run and cleanup for mysql_recreate_table() */ + +static bool admin_recreate_table(THD *thd, TABLE_LIST *table_list, + Recreate_info *recreate_info) +{ + bool result_code; + DBUG_ENTER("admin_recreate_table"); + + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); + thd->release_transactional_locks(); + + /* + table_list->table has been closed and freed. Do not reference + uninitialized data. open_tables() could fail. + */ + table_list->table= NULL; + /* Same applies to MDL ticket. */ + table_list->mdl_request.ticket= NULL; + + DEBUG_SYNC(thd, "ha_admin_try_alter"); + tmp_disable_binlog(thd); // binlogging is done by caller if wanted + result_code= (thd->open_temporary_tables(table_list) || + mysql_recreate_table(thd, table_list, recreate_info, false)); + reenable_binlog(thd); + /* + mysql_recreate_table() can push OK or ERROR. + Clear 'OK' status. If there is an error, keep it: + we will store the error message in a result set row + and then clear. + */ + if (thd->get_stmt_da()->is_ok()) + thd->get_stmt_da()->reset_diagnostics_area(); + table_list->table= NULL; + DBUG_RETURN(result_code); +} + + +static int send_check_errmsg(THD *thd, TABLE_LIST* table, + const LEX_CSTRING *operator_name, + const char* errmsg) + +{ + Protocol *protocol= thd->protocol; + protocol->prepare_for_resend(); + protocol->store(table->alias.str, table->alias.length, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(&error_clex_str, system_charset_info); + protocol->store(errmsg, strlen(errmsg), system_charset_info); + thd->clear_error(); + if (protocol->write()) + return -1; + return 1; +} + + +static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, + HA_CHECK_OPT *check_opt) +{ + int error= 0, create_error= 0; + TABLE tmp_table, *table; + TABLE_LIST *pos_in_locked_tables= 0; + TABLE_SHARE *share= 0; + bool has_mdl_lock= FALSE; + char from[FN_REFLEN],tmp[FN_REFLEN+32]; + const char **ext; + MY_STAT stat_info; + Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | + MYSQL_OPEN_HAS_MDL_LOCK | + MYSQL_LOCK_IGNORE_TIMEOUT)); + DBUG_ENTER("prepare_for_repair"); + + if (!(check_opt->sql_flags & TT_USEFRM)) + DBUG_RETURN(0); + + if (!(table= table_list->table)) + { + /* + If the table didn't exist, we have a shared metadata lock + on it that is left from mysql_admin_table()'s attempt to + open it. Release the shared metadata lock before trying to + acquire the exclusive lock to satisfy MDL asserts and avoid + deadlocks. + */ + thd->release_transactional_locks(); + /* + Attempt to do full-blown table open in mysql_admin_table() has failed. + Let us try to open at least a .FRM for this table. + */ + + MDL_REQUEST_INIT(&table_list->mdl_request, MDL_key::TABLE, + table_list->db.str, table_list->table_name.str, + MDL_EXCLUSIVE, MDL_TRANSACTION); + + if (lock_table_names(thd, table_list, table_list->next_global, + thd->variables.lock_wait_timeout, 0)) + DBUG_RETURN(0); + has_mdl_lock= TRUE; + + share= tdc_acquire_share(thd, table_list, GTS_TABLE); + if (share == NULL) + DBUG_RETURN(0); // Can't open frm file + + if (open_table_from_share(thd, share, &empty_clex_str, 0, 0, 0, + &tmp_table, FALSE)) + { + tdc_release_share(share); + DBUG_RETURN(0); // Out of memory + } + table= &tmp_table; + } + + /* + REPAIR TABLE ... USE_FRM for temporary tables makes little sense. + */ + if (table->s->tmp_table) + { + error= send_check_errmsg(thd, table_list, &msg_repair, + "Cannot repair temporary table from .frm file"); + goto end; + } + + /* + User gave us USE_FRM which means that the header in the index file is + trashed. + In this case we will try to fix the table the following way: + - Rename the data file to a temporary name + - Truncate the table + - Replace the new data file with the old one + - Run a normal repair using the new index file and the old data file + */ + + if (table->s->frm_version < FRM_VER_TRUE_VARCHAR && + table->s->varchar_fields) + { + error= send_check_errmsg(thd, table_list, &msg_repair, + "Failed repairing a very old .frm file as the " + "data file format has changed between versions. " + "Please dump the table in your old system with " + "mysqldump and read it into this system with " + "mysql or mysqlimport"); + goto end; + } + + /* + Check if this is a table type that stores index and data separately, + like ISAM or MyISAM. We assume fixed order of engine file name + extensions array. First element of engine file name extensions array + is meta/index file extention. Second element - data file extention. + */ + ext= table->file->bas_ext(); + if (!ext[0] || !ext[1]) + goto end; // No data file + + /* A MERGE table must not come here. */ + DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM); + + // Name of data file + strxmov(from, table->s->normalized_path.str, ext[1], NullS); + if (!mysql_file_stat(key_file_misc, from, &stat_info, MYF(0))) + goto end; // Can't use USE_FRM flag + + my_snprintf(tmp, sizeof(tmp), "%s-%lx_%llx", + from, current_pid, thd->thread_id); + + if (table_list->table) + { + /* + Table was successfully open in mysql_admin_table(). Now we need + to close it, but leave it protected by exclusive metadata lock. + */ + pos_in_locked_tables= table->pos_in_locked_tables; + if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_FORCED_CLOSE)) + goto end; + /* Close table but don't remove from locked list */ + close_all_tables_for_name(thd, table_list->table->s, + HA_EXTRA_NOT_USED, NULL); + table_list->table= 0; + } + else + { + /* + Table open failed, maybe because we run out of memory. + Close all open tables and relaese all MDL locks + */ + tdc_release_share(share); + share->tdc->flush(thd, true); + share= 0; + } + + /* + After this point we have an exclusive metadata lock on our table + in both cases when table was successfully open in mysql_admin_table() + and when it was open in prepare_for_repair(). + */ + + if (my_rename(from, tmp, MYF(MY_WME))) + { + error= send_check_errmsg(thd, table_list, &msg_repair, + "Failed renaming data file"); + goto end; + } + if (dd_recreate_table(thd, table_list->db.str, table_list->table_name.str)) + create_error= send_check_errmsg(thd, table_list, &msg_repair, + "Failed generating table from .frm file"); + /* + 'FALSE' for 'using_transactions' means don't postpone + invalidation till the end of a transaction, but do it + immediately. + */ + query_cache_invalidate3(thd, table_list, FALSE); + if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME))) + { + error= send_check_errmsg(thd, table_list, &msg_repair, + "Failed restoring .MYD file"); + goto end; + } + if (create_error) + goto end; + + if (thd->locked_tables_list.locked_tables()) + { + if (thd->locked_tables_list.reopen_tables(thd, false)) + goto end; + /* Restore the table in the table list with the new opened table */ + table_list->table= pos_in_locked_tables->table; + } + else + { + /* + Now we should be able to open the partially repaired table + to finish the repair in the handler later on. + */ + if (open_table(thd, table_list, &ot_ctx)) + { + error= send_check_errmsg(thd, table_list, &msg_repair, + "Failed to open partially repaired table"); + goto end; + } + } + +end: + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + if (table == &tmp_table) + { + closefrm(table); + if (share) + tdc_release_share(share); + } + /* In case of a temporary table there will be no metadata lock. */ + if (unlikely(error) && has_mdl_lock) + thd->release_transactional_locks(); + + DBUG_RETURN(error); +} + + +/** + Check if a given error is something that could occur during + open_and_lock_tables() that does not indicate table corruption. + + @param sql_errno Error number to check. + + @retval TRUE Error does not indicate table corruption. + @retval FALSE Error could indicate table corruption. +*/ + +static inline bool table_not_corrupt_error(uint sql_errno) +{ + return (sql_errno == ER_NO_SUCH_TABLE || + sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE || + sql_errno == ER_FILE_NOT_FOUND || + sql_errno == ER_LOCK_WAIT_TIMEOUT || + sql_errno == ER_LOCK_DEADLOCK || + sql_errno == ER_CANT_LOCK_LOG_TABLE || + sql_errno == ER_OPEN_AS_READONLY || + sql_errno == ER_WRONG_OBJECT); +} + +#ifndef DBUG_OFF +// It is counter for debugging fail on second call of open_only_one_table +static int debug_fail_counter= 0; +#endif + +static bool open_only_one_table(THD* thd, TABLE_LIST* table, + bool repair_table_use_frm, + bool is_view_operator_func) +{ + LEX *lex= thd->lex; + SELECT_LEX *select= lex->first_select_lex(); + TABLE_LIST *save_next_global, *save_next_local; + bool open_error; + save_next_global= table->next_global; + table->next_global= 0; + save_next_local= table->next_local; + table->next_local= 0; + select->table_list.first= table; + /* + Time zone tables and SP tables can be add to lex->query_tables list, + so it have to be prepared. + TODO: Investigate if we can put extra tables into argument instead of + using lex->query_tables + */ + lex->query_tables= table; + lex->query_tables_last= &table->next_global; + lex->query_tables_own_last= 0; + + DBUG_EXECUTE_IF("fail_2call_open_only_one_table", { + if (debug_fail_counter) + { + open_error= TRUE; + goto dbug_err; + } + else + debug_fail_counter++; + }); + + /* + CHECK TABLE command is allowed for views as well. Check on alter flags + to differentiate from ALTER TABLE...CHECK PARTITION on which view is not + allowed. + */ + if (lex->alter_info.partition_flags & ALTER_PARTITION_ADMIN || + !is_view_operator_func) + { + table->required_type= TABLE_TYPE_NORMAL; + DBUG_ASSERT(lex->table_type != TABLE_TYPE_VIEW); + } + else if (lex->table_type == TABLE_TYPE_VIEW) + { + table->required_type= lex->table_type; + } + else if ((lex->table_type != TABLE_TYPE_VIEW) && + lex->sql_command == SQLCOM_REPAIR) + { + table->required_type= TABLE_TYPE_NORMAL; + } + + if (lex->sql_command == SQLCOM_CHECK || + lex->sql_command == SQLCOM_REPAIR || + lex->sql_command == SQLCOM_ANALYZE || + lex->sql_command == SQLCOM_OPTIMIZE) + thd->prepare_derived_at_open= TRUE; + if (!thd->locked_tables_mode && repair_table_use_frm) + { + /* + If we're not under LOCK TABLES and we're executing REPAIR TABLE + USE_FRM, we need to ignore errors from open_and_lock_tables(). + REPAIR TABLE USE_FRM is a heavy weapon used when a table is + critically damaged, so open_and_lock_tables() will most likely + report errors. Those errors are not interesting for the user + because it's already known that the table is badly damaged. + */ + + Diagnostics_area *da= thd->get_stmt_da(); + Warning_info tmp_wi(thd->query_id, false, true); + + da->push_warning_info(&tmp_wi); + + open_error= (thd->open_temporary_tables(table) || + open_and_lock_tables(thd, table, TRUE, 0)); + + da->pop_warning_info(); + } + else + { + /* + It's assumed that even if it is REPAIR TABLE USE_FRM, the table + can be opened if we're under LOCK TABLES (otherwise LOCK TABLES + would fail). Thus, the only errors we could have from + open_and_lock_tables() are logical ones, like incorrect locking + mode. It does make sense for the user to see such errors. + */ + + open_error= (thd->open_temporary_tables(table) || + open_and_lock_tables(thd, table, TRUE, 0)); + } + +#ifndef DBUG_OFF +dbug_err: +#endif + + thd->prepare_derived_at_open= FALSE; + + /* + MERGE engine may adjust table->next_global chain, thus we have to + append save_next_global after merge children. + */ + if (save_next_global) + { + TABLE_LIST *table_list_iterator= table; + while (table_list_iterator->next_global) + table_list_iterator= table_list_iterator->next_global; + table_list_iterator->next_global= save_next_global; + save_next_global->prev_global= &table_list_iterator->next_global; + } + + table->next_local= save_next_local; + + return open_error; +} + +#ifdef WITH_WSREP +/** RAII class for temporarily disable wsrep_on in the connection. */ +class Disable_wsrep_on_guard +{ + public: + /** + @param thd - pointer to the context of connection in which + wsrep_on mode needs to be disabled. + @param disable - true if wsrep_on should be disabled + */ + explicit Disable_wsrep_on_guard(THD *thd, bool disable) + : m_thd(thd), m_orig_wsrep_on(thd->variables.wsrep_on) + { + if (disable) + thd->variables.wsrep_on= false; + } + + ~Disable_wsrep_on_guard() + { + m_thd->variables.wsrep_on= m_orig_wsrep_on; + } + private: + THD* m_thd; + bool m_orig_wsrep_on; +}; +#endif /* WITH_WSREP */ + + +static void send_read_only_warning(THD *thd, const LEX_CSTRING *msg_status, + const LEX_CSTRING *table_name) +{ + Protocol *protocol= thd->protocol; + char buf[MYSQL_ERRMSG_SIZE]; + size_t length; + length= my_snprintf(buf, sizeof(buf), + ER_THD(thd, ER_OPEN_AS_READONLY), + table_name->str); + protocol->store(msg_status, system_charset_info); + protocol->store(buf, length, system_charset_info); +} + + +/** + Collect field names of result set that will be sent to a client + + @param thd Thread data object + @param[out] fields List of fields whose metadata should be collected for + sending to client +*/ + +void fill_check_table_metadata_fields(THD *thd, List<Item>* fields) +{ + Item *item; + + item= new (thd->mem_root) Item_empty_string(thd, "Table", NAME_CHAR_LEN * 2); + item->set_maybe_null(); + fields->push_back(item, thd->mem_root); + + item= new (thd->mem_root) Item_empty_string(thd, "Op", 10); + item->set_maybe_null(); + fields->push_back(item, thd->mem_root); + + item= new (thd->mem_root) Item_empty_string(thd, "Msg_type", 10); + item->set_maybe_null(); + fields->push_back(item, thd->mem_root); + + item= new (thd->mem_root) Item_empty_string(thd, "Msg_text", + SQL_ADMIN_MSG_TEXT_SIZE); + item->set_maybe_null(); + fields->push_back(item, thd->mem_root); +} + + +/* + RETURN VALUES + FALSE Message sent to net (admin operation went ok) + TRUE Message should be sent by caller + (admin operation or network communication failed) +*/ +static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, + HA_CHECK_OPT* check_opt, + const LEX_CSTRING *operator_name, + thr_lock_type lock_type, + bool org_open_for_modify, + bool repair_table_use_frm, + uint extra_open_options, + int (*prepare_func)(THD *, TABLE_LIST *, + HA_CHECK_OPT *), + int (handler::*operator_func)(THD *, + HA_CHECK_OPT *), + int (view_operator_func)(THD *, TABLE_LIST*, + HA_CHECK_OPT *), + bool is_cmd_replicated) +{ + TABLE_LIST *table; + List<Item> field_list; + Protocol *protocol= thd->protocol; + LEX *lex= thd->lex; + bool need_repair_or_alter= 0; + wait_for_commit* suspended_wfc; + bool is_table_modified= false; + LEX_CUSTRING tabledef_version; + DBUG_ENTER("mysql_admin_table"); + DBUG_PRINT("enter", ("extra_open_options: %u", extra_open_options)); + +#ifdef WITH_WSREP + /* + CACHE INDEX and LOAD INDEX INTO CACHE statements are + local operations. Do not replicate them with Galera + */ + const bool disable_wsrep_on= (WSREP(thd) && + (lex->sql_command == SQLCOM_ASSIGN_TO_KEYCACHE || + lex->sql_command == SQLCOM_PRELOAD_KEYS)); + + Disable_wsrep_on_guard wsrep_on_guard(thd, disable_wsrep_on); +#endif /* WITH_WSREP */ + + fill_check_table_metadata_fields(thd, &field_list); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + /* + This function calls trans_commit() during its operation, but that does not + imply that the operation is complete or binlogged. So we have to suspend + temporarily the wakeup_subsequent_commits() calls (if used). + */ + suspended_wfc= thd->suspend_subsequent_commits(); + + mysql_ha_rm_tables(thd, tables); + + /* + Close all temporary tables which were pre-open to simplify + privilege checking. Clear all references to closed tables. + */ + close_thread_tables(thd); + for (table= tables; table; table= table->next_local) + table->table= NULL; + + for (table= tables; table; table= table->next_local) + { + char table_name_buff[SAFE_NAME_LEN*2+2]; + LEX_CSTRING table_name= { table_name_buff, 0}; + char storage_engine_name[NAME_LEN]; + bool storage_engine_partitioned= 0; + uchar tabledef_version_buff[MY_UUID_SIZE]; + const char *db= table->db.str; + bool fatal_error=0; + bool open_error= 0; + bool collect_eis= FALSE; + bool open_for_modify= org_open_for_modify; + Recreate_info recreate_info; + int compl_result_code, result_code; + + compl_result_code= result_code= HA_ADMIN_FAILED; + storage_engine_name[0]= 0; // Marker that's not used + + DBUG_PRINT("admin", ("table: '%s'.'%s'", db, table->table_name.str)); + DEBUG_SYNC(thd, "admin_command_kill_before_modify"); + + table_name.length= strxmov(table_name_buff, db, ".", table->table_name.str, + NullS) - table_name_buff; + thd->open_options|= extra_open_options; + table->lock_type= lock_type; + /* + To make code safe for re-execution we need to reset type of MDL + request as code below may change it. + To allow concurrent execution of read-only operations we acquire + weak metadata lock for them. + */ + table->mdl_request.set_type(lex->sql_command == SQLCOM_REPAIR + ? MDL_SHARED_NO_READ_WRITE + : lock_type >= TL_FIRST_WRITE + ? MDL_SHARED_WRITE : MDL_SHARED_READ); + if (thd->check_killed()) + { + open_error= false; + fatal_error= true; + result_code= HA_ADMIN_FAILED; + goto send_result; + } + + /* open only one table from local list of command */ + while (1) + { + open_error= open_only_one_table(thd, table, + repair_table_use_frm, + (view_operator_func != NULL)); + thd->open_options&= ~extra_open_options; + + /* + If open_and_lock_tables() failed, close_thread_tables() will close + the table and table->table can therefore be invalid. + */ + if (unlikely(open_error)) + table->table= NULL; + + /* + Under locked tables, we know that the table can be opened, + so any errors opening the table are logical errors. + In these cases it does not make sense to try to repair. + */ + if (unlikely(open_error) && thd->locked_tables_mode) + { + result_code= HA_ADMIN_FAILED; + goto send_result; + } + + if (!table->table || table->mdl_request.type != MDL_SHARED_WRITE || + table->table->file->ha_table_flags() & HA_CONCURRENT_OPTIMIZE) + break; + + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); + table->table= NULL; + thd->release_transactional_locks(); + MDL_REQUEST_INIT(&table->mdl_request, MDL_key::TABLE, table->db.str, + table->table_name.str, MDL_SHARED_NO_READ_WRITE, + MDL_TRANSACTION); + } + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->table) + { + /* + Set up which partitions that should be processed + if ALTER TABLE t ANALYZE/CHECK/OPTIMIZE/REPAIR PARTITION .. + CACHE INDEX/LOAD INDEX for specified partitions + */ + Alter_info *alter_info= &lex->alter_info; + + if (alter_info->partition_flags & ALTER_PARTITION_ADMIN) + { + if (!table->table->part_info) + { + my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + thd->resume_subsequent_commits(suspended_wfc); + DBUG_RETURN(TRUE); + } + if (set_part_state(alter_info, table->table->part_info, PART_ADMIN)) + { + char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; + size_t length; + DBUG_PRINT("admin", ("sending non existent partition error")); + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(&error_clex_str, system_charset_info); + length= my_snprintf(buff, sizeof(buff), + ER_THD(thd, ER_PARTITION_DOES_NOT_EXIST)); + protocol->store(buff, length, system_charset_info); + if(protocol->write()) + goto err; + my_eof(thd); + goto err; + } + } + } +#endif + DBUG_PRINT("admin", ("table: %p", table->table)); + + if (table->schema_table) + { + result_code= HA_ADMIN_NOT_IMPLEMENTED; + goto send_result; + } + + if (prepare_func) + { + DBUG_PRINT("admin", ("calling prepare_func")); + switch ((*prepare_func)(thd, table, check_opt)) { + case 1: // error, message written to net + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); + thd->release_transactional_locks(); + DBUG_PRINT("admin", ("simple error, admin next table")); + continue; + case -1: // error, message could be written to net + /* purecov: begin inspected */ + DBUG_PRINT("admin", ("severe error, stop")); + goto err; + /* purecov: end */ + default: // should be 0 otherwise + DBUG_PRINT("admin", ("prepare_func succeeded")); + ; + } + } + + /* + CHECK/REPAIR TABLE command is only command where VIEW allowed here and + this command use only temporary table method for VIEWs resolving => + there can't be VIEW tree substitition of join view => if opening table + succeed then table->table will have real TABLE pointer as value (in + case of join view substitution table->table can be 0, but here it is + impossible) + */ + if (!table->table) + { + DBUG_PRINT("admin", ("open table failed")); + if (thd->get_stmt_da()->is_warning_info_empty()) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CHECK_NO_SUCH_TABLE, + ER_THD(thd, ER_CHECK_NO_SUCH_TABLE)); + /* if it was a view will check md5 sum */ + if (table->view && + view_check(thd, table, check_opt) == HA_ADMIN_WRONG_CHECKSUM) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_VIEW_CHECKSUM, ER_THD(thd, ER_VIEW_CHECKSUM)); + if (thd->get_stmt_da()->is_error() && + table_not_corrupt_error(thd->get_stmt_da()->sql_errno())) + result_code= HA_ADMIN_FAILED; + else + /* Default failure code is corrupt table */ + result_code= HA_ADMIN_CORRUPT; + goto send_result; + } + + if (table->view) + { + DBUG_PRINT("admin", ("calling view_operator_func")); + result_code= (*view_operator_func)(thd, table, check_opt); + goto send_result; + } + + if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify && + operator_func != &handler::ha_analyze) + { + /* purecov: begin inspected */ + enum_sql_command save_sql_command= lex->sql_command; + DBUG_PRINT("admin", ("sending error message")); + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + send_read_only_warning(thd, &error_clex_str, &table_name); + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->release_transactional_locks(); + lex->reset_query_tables_list(FALSE); + /* + Restore Query_tables_list::sql_command value to make statement + safe for re-execution. + */ + lex->sql_command= save_sql_command; + table->table=0; // For query cache + if (protocol->write()) + goto err; + thd->get_stmt_da()->reset_diagnostics_area(); + continue; + /* purecov: end */ + } + + /* + Close all instances of the table to allow MyISAM "repair" + (which is internally also used from "optimize") to rename files. + @todo: This code does not close all instances of the table. + It only closes instances in other connections, but if this + connection has LOCK TABLE t1 a READ, t1 b WRITE, + both t1 instances will be kept open. + + Note that this code is only executed for engines that request + MDL_SHARED_NO_READ_WRITE lock (MDL_SHARED_WRITE cannot be upgraded) + by *not* having HA_CONCURRENT_OPTIMIZE table_flag. + */ + if (lock_type == TL_WRITE && table->mdl_request.type > MDL_SHARED_WRITE) + { + if (table->table->s->tmp_table) + thd->close_unused_temporary_table_instances(tables); + else + { + /* Store information about table for ddl log */ + storage_engine_partitioned= table->table->file->partition_engine(); + strmake(storage_engine_name, table->table->file->real_table_type(), + sizeof(storage_engine_name)-1); + tabledef_version.str= tabledef_version_buff; + if ((tabledef_version.length= table->table->s->tabledef_version.length)) + memcpy((char*) tabledef_version.str, + table->table->s->tabledef_version.str, + MY_UUID_SIZE); + + if (wait_while_table_is_used(thd, table->table, HA_EXTRA_NOT_USED)) + goto err; + DEBUG_SYNC(thd, "after_admin_flush"); + /* Flush entries in the query cache involving this table. */ + query_cache_invalidate3(thd, table->table, 0); + /* + XXX: hack: switch off open_for_modify to skip the + flush that is made later in the execution flow. + */ + open_for_modify= 0; + } + } + + if (table->table->s->crashed && operator_func == &handler::ha_check) + { + /* purecov: begin inspected */ + DBUG_PRINT("admin", ("sending crashed warning")); + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("warning"), system_charset_info); + protocol->store(STRING_WITH_LEN("Table is marked as crashed"), + system_charset_info); + if (protocol->write()) + goto err; + /* purecov: end */ + } + + if (operator_func == &handler::ha_repair && + !(check_opt->sql_flags & TT_USEFRM)) + { + handler *file= table->table->file; + int check_old_types= file->check_old_types(); + int check_for_upgrade= file->ha_check_for_upgrade(check_opt); + + if (check_old_types == HA_ADMIN_NEEDS_ALTER || + check_for_upgrade == HA_ADMIN_NEEDS_ALTER) + { + /* We use extra_open_options to be able to open crashed tables */ + thd->open_options|= extra_open_options; + result_code= admin_recreate_table(thd, table, &recreate_info) ? + HA_ADMIN_FAILED : HA_ADMIN_OK; + thd->open_options&= ~extra_open_options; + goto send_result; + } + if (check_old_types || check_for_upgrade) + { + /* If repair is not implemented for the engine, run ALTER TABLE */ + need_repair_or_alter= 1; + } + } + + result_code= compl_result_code= HA_ADMIN_OK; + + if (operator_func == &handler::ha_analyze) + { + TABLE *tab= table->table; + + if (lex->with_persistent_for_clause && + tab->s->table_category != TABLE_CATEGORY_USER) + { + compl_result_code= result_code= HA_ADMIN_INVALID; + } + + /* + The check for ALTER_PARTITION_ADMIN implements this logic: + do not collect EITS STATS for this syntax: + ALTER TABLE ... ANALYZE PARTITION p + EITS statistics is global (not per-partition). Collecting global stats + is much more expensive processing just one partition, so the most + appropriate action is to just not collect EITS stats for this command. + */ + collect_eis= + (table->table->s->table_category == TABLE_CATEGORY_USER && + !(lex->alter_info.flags & ALTER_PARTITION_ADMIN) && + (check_eits_collection_allowed(thd) || + lex->with_persistent_for_clause)); + } + + if (result_code == HA_ADMIN_OK) + { + DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name->str)); + THD_STAGE_INFO(thd, stage_executing); + result_code = (table->table->file->*operator_func)(thd, check_opt); + THD_STAGE_INFO(thd, stage_sending_data); + DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); + } + + /* Note: compl_result_code can be different from result_code here */ + if (compl_result_code == HA_ADMIN_OK && collect_eis) + { + if (result_code == HA_ERR_TABLE_READONLY) + { + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + send_read_only_warning(thd, &msg_status, &table_name); + (void) protocol->write(); + result_code= HA_ADMIN_OK; + } + /* + Here we close and reopen table in read mode because operation of + collecting statistics is long and it will be better do not block + the table completely. + InnoDB will allow read/write and MyISAM read/insert. + */ + trans_commit_stmt(thd); + trans_commit(thd); + thd->open_options|= extra_open_options; + close_thread_tables(thd); + table->table= NULL; + thd->release_transactional_locks(); + MDL_REQUEST_INIT(&table->mdl_request, MDL_key::TABLE, table->db.str, + table->table_name.str, MDL_SHARED_NO_READ_WRITE, + MDL_TRANSACTION); + table->mdl_request.set_type(MDL_SHARED_READ); + + table->lock_type= TL_READ; + DBUG_ASSERT(view_operator_func == NULL); + open_error= open_only_one_table(thd, table, + repair_table_use_frm, FALSE); + thd->open_options&= ~extra_open_options; + + if (unlikely(!open_error)) + { + TABLE *tab= table->table; + Field **field_ptr= tab->field; + USED_MEM *memroot_block; + + if (!lex->column_list) + { + /* Fields we have to read from the engine */ + bitmap_clear_all(tab->read_set); + /* Fields we want to have statistics for */ + bitmap_clear_all(&tab->has_value_set); + + for (uint fields= 0; *field_ptr; field_ptr++, fields++) + { + Field *field= *field_ptr; + if (field->flags & LONG_UNIQUE_HASH_FIELD) + { + /* + No point in doing statistic for hash fields that should be + unique + */ + continue; + } + /* + Note that type() always return MYSQL_TYPE_BLOB for + all blob types. Another function needs to be added + if we in the future want to distingush between blob + types here. + */ + enum enum_field_types type= field->type(); + if (type < MYSQL_TYPE_TINY_BLOB || + type > MYSQL_TYPE_BLOB) + { + field->register_field_in_read_map(); + bitmap_set_bit(&tab->has_value_set, field->field_index); + } + else + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NO_EIS_FOR_FIELD, + ER_THD(thd, ER_NO_EIS_FOR_FIELD), + (*field_ptr)->field_name.str); + } + } + else + { + int pos; + LEX_STRING *column_name; + List_iterator_fast<LEX_STRING> it(*lex->column_list); + + /* Fields we have to read from the engine */ + bitmap_clear_all(tab->read_set); + /* Fields we want to have statistics for */ + bitmap_clear_all(&tab->has_value_set); + + while ((column_name= it++)) + { + Field *field; + enum enum_field_types type; + if (tab->s->fieldnames.type_names == 0 || + (pos= find_type(&tab->s->fieldnames, column_name->str, + column_name->length, 1)) <= 0) + { + compl_result_code= result_code= HA_ADMIN_INVALID; + break; + } + pos--; + field= tab->field[pos]; + type= field->type(); + if (!(field->flags & LONG_UNIQUE_HASH_FIELD) && + (type < MYSQL_TYPE_TINY_BLOB || + type > MYSQL_TYPE_BLOB)) + { + field->register_field_in_read_map(); + bitmap_set_bit(&tab->has_value_set, field->field_index); + } + else + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NO_EIS_FOR_FIELD, + ER_THD(thd, ER_NO_EIS_FOR_FIELD), + column_name->str); + } + tab->file->column_bitmaps_signal(); + } + if (!lex->index_list) + tab->keys_in_use_for_query.init(tab->s->keys); + else + { + int pos; + LEX_STRING *index_name; + List_iterator_fast<LEX_STRING> it(*lex->index_list); + tab->keys_in_use_for_query.clear_all(); + while ((index_name= it++)) + { + if (tab->s->keynames.type_names == 0 || + (pos= find_type(&tab->s->keynames, index_name->str, + index_name->length, 1)) <= 0) + { + compl_result_code= result_code= HA_ADMIN_INVALID; + break; + } + tab->keys_in_use_for_query.set_bit(--pos); + } + } + /* Ensure that number of records are updated */ + tab->file->info(HA_STATUS_VARIABLE); + memroot_block= get_last_memroot_block(thd->mem_root); + if (!(compl_result_code= + alloc_statistics_for_table(thd, tab, + &tab->has_value_set)) && + !(compl_result_code= + collect_statistics_for_table(thd, tab))) + compl_result_code= update_statistics_for_table(thd, tab); + free_statistics_for_table(tab); + free_all_new_blocks(thd->mem_root, memroot_block); + } + else + compl_result_code= HA_ADMIN_FAILED; + + if (compl_result_code) + result_code= HA_ADMIN_FAILED; + else + { + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(&msg_status, system_charset_info); + protocol->store(STRING_WITH_LEN("Engine-independent statistics collected"), + system_charset_info); + if (protocol->write()) + goto err; + } + } + + if (result_code == HA_ADMIN_NOT_IMPLEMENTED && need_repair_or_alter) + { + /* + repair was not implemented and we need to upgrade the table + to a new version so we recreate the table with ALTER TABLE + */ + result_code= admin_recreate_table(thd, table, &recreate_info); + } +send_result: + + lex->cleanup_after_one_table_open(); + thd->clear_error(); // these errors shouldn't get client + + if (recreate_info.records_duplicate()) + { + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(warning_level_names[Sql_condition::WARN_LEVEL_WARN].str, + warning_level_names[Sql_condition::WARN_LEVEL_WARN].length, + system_charset_info); + char buf[80]; + size_t length= my_snprintf(buf, sizeof(buf), + "Number of rows changed from %u to %u", + (uint) recreate_info.records_processed(), + (uint) recreate_info.records_copied()); + protocol->store(buf, length, system_charset_info); + if (protocol->write()) + goto err; + } + + { + Diagnostics_area::Sql_condition_iterator it= + thd->get_stmt_da()->sql_conditions(); + const Sql_condition *err; + while ((err= it++)) + { + const char *err_msg= err->get_message_text(); + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(warning_level_names[err->get_level()].str, + warning_level_names[err->get_level()].length, + system_charset_info); + protocol->store(err_msg, strlen(err_msg), system_charset_info); + if (protocol->write()) + goto err; + } + thd->get_stmt_da()->clear_warning_info(thd->query_id); + } + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + +send_result_message: + + DBUG_PRINT("info", ("result_code: %d", result_code)); + switch (result_code) { + case HA_ADMIN_NOT_IMPLEMENTED: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length=my_snprintf(buf, sizeof(buf), + ER_THD(thd, ER_CHECK_NOT_IMPLEMENTED), + operator_name->str); + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(buf, length, system_charset_info); + } + break; + + case HA_ADMIN_NOT_BASE_TABLE: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length= my_snprintf(buf, sizeof(buf), + ER_THD(thd, ER_BAD_TABLE_ERROR), + table_name.str); + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(buf, length, system_charset_info); + } + break; + + case HA_ADMIN_OK: + protocol->store(&msg_status, system_charset_info); + protocol->store(STRING_WITH_LEN("OK"), system_charset_info); + break; + + case HA_ADMIN_FAILED: + protocol->store(&msg_status, system_charset_info); + protocol->store(STRING_WITH_LEN("Operation failed"), + system_charset_info); + break; + + case HA_ADMIN_REJECT: + protocol->store(&msg_status, system_charset_info); + protocol->store(STRING_WITH_LEN("Operation need committed state"), + system_charset_info); + open_for_modify= FALSE; + break; + + case HA_ADMIN_ALREADY_DONE: + protocol->store(&msg_status, system_charset_info); + protocol->store(STRING_WITH_LEN("Table is already up to date"), + system_charset_info); + break; + + case HA_ADMIN_CORRUPT: + protocol->store(&error_clex_str, system_charset_info); + protocol->store(STRING_WITH_LEN("Corrupt"), system_charset_info); + fatal_error=1; + break; + + case HA_ADMIN_INVALID: + protocol->store(&error_clex_str, system_charset_info); + protocol->store(STRING_WITH_LEN("Invalid argument"), + system_charset_info); + break; + + case HA_ADMIN_TRY_ALTER: + { + Alter_info *alter_info= &lex->alter_info; + + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + if (alter_info->partition_flags & ALTER_PARTITION_ADMIN) + { + protocol->store(STRING_WITH_LEN( + "Table does not support optimize on partitions. All partitions " + "will be rebuilt and analyzed."),system_charset_info); + } + else + { + protocol->store(STRING_WITH_LEN( + "Table does not support optimize, doing recreate + analyze instead"), + system_charset_info); + } + if (protocol->write()) + goto err; + THD_STAGE_INFO(thd, stage_recreating_table); + DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); + TABLE_LIST *save_next_local= table->next_local, + *save_next_global= table->next_global; + table->next_local= table->next_global= 0; + + result_code= admin_recreate_table(thd, table, &recreate_info); + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->release_transactional_locks(); + /* Clear references to TABLE and MDL_ticket after releasing them. */ + table->mdl_request.ticket= NULL; + + if (!result_code) // recreation went ok + { + /* Clear the ticket released above. */ + table->mdl_request.ticket= NULL; + DEBUG_SYNC(thd, "ha_admin_open_ltable"); + table->mdl_request.set_type(MDL_SHARED_WRITE); + if (!thd->open_temporary_tables(table) && + (table->table= open_ltable(thd, table, lock_type, 0))) + { + ulonglong save_flags; + /* Store the original value of alter_info->flags */ + save_flags= alter_info->flags; + + /* + Reset the ALTER_PARTITION_ADMIN bit in alter_info->flags + to force analyze on all partitions. + */ + alter_info->partition_flags &= ~(ALTER_PARTITION_ADMIN); + result_code= table->table->file->ha_analyze(thd, check_opt); + if (result_code == HA_ADMIN_ALREADY_DONE) + result_code= HA_ADMIN_OK; + else if (result_code) // analyze failed + table->table->file->print_error(result_code, MYF(0)); + alter_info->flags= save_flags; + } + else + result_code= -1; // open failed + } + /* Start a new row for the final status row */ + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + if (result_code) // either mysql_recreate_table or analyze failed + { + DBUG_ASSERT(thd->is_error()); + if (thd->is_error()) + { + const char *err_msg= thd->get_stmt_da()->message(); + if (!thd->vio_ok()) + { + sql_print_error("%s", err_msg); + } + else + { + /* Hijack the row already in-progress. */ + protocol->store(&error_clex_str, system_charset_info); + protocol->store(err_msg, strlen(err_msg), system_charset_info); + if (protocol->write()) + goto err; + /* Start off another row for HA_ADMIN_FAILED */ + protocol->prepare_for_resend(); + protocol->store(&table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + } + thd->clear_error(); + } + /* Make sure this table instance is not reused after the operation. */ + if (table->table) + table->table->mark_table_for_reopen(); + } + result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; + table->next_local= save_next_local; + table->next_global= save_next_global; + goto send_result_message; + } + case HA_ADMIN_WRONG_CHECKSUM: + { + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(ER_THD(thd, ER_VIEW_CHECKSUM), + strlen(ER_THD(thd, ER_VIEW_CHECKSUM)), + system_charset_info); + break; + } + + case HA_ADMIN_NEEDS_UPGRADE: + case HA_ADMIN_NEEDS_ALTER: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length; + const char *what_to_upgrade= table->view ? "VIEW" : + table->table->file->ha_table_flags() & HA_CAN_REPAIR ? "TABLE" : 0; + + protocol->store(&error_clex_str, system_charset_info); + if (what_to_upgrade) + length= my_snprintf(buf, sizeof(buf), + ER_THD(thd, ER_TABLE_NEEDS_UPGRADE), + what_to_upgrade, table->table_name.str); + else + length= my_snprintf(buf, sizeof(buf), + ER_THD(thd, ER_TABLE_NEEDS_REBUILD), + table->table_name.str); + protocol->store(buf, length, system_charset_info); + fatal_error=1; + break; + } + case HA_ERR_TABLE_READONLY: + { + send_read_only_warning(thd, &msg_status, &table_name); + break; + } + default: // Probably HA_ADMIN_INTERNAL_ERROR + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length=my_snprintf(buf, sizeof(buf), + "Unknown - internal error %d during operation", + result_code); + protocol->store(&error_clex_str, system_charset_info); + protocol->store(buf, length, system_charset_info); + fatal_error=1; + break; + } + } + /* + Admin commands acquire table locks and these locks are not detected by + parallel replication deadlock detection-and-handling mechanism. Hence + they must be marked as DDL so that they are not scheduled in parallel + with conflicting DMLs resulting in deadlock. + */ + thd->transaction->stmt.mark_executed_table_admin_cmd(); + + if (table->table && !table->view) + { + /* Skip FLUSH TABLES if we are doing analyze */ + const bool skip_flush= (operator_func == &handler::ha_analyze); + if (table->table->s->tmp_table) + { + /* + If the table was not opened successfully, do not try to get + status information. (Bug#47633) + */ + if (open_for_modify && !open_error) + table->table->file->info(HA_STATUS_CONST); + } + else if ((!skip_flush && open_for_modify) || fatal_error) + { + table->table->s->tdc->flush_unused(true); + /* + May be something modified. Consequently, we have to + invalidate the query cache. + */ + table->table= 0; // For query cache + query_cache_invalidate3(thd, table, 0); + } + else if (collect_eis && skip_flush && compl_result_code == HA_ADMIN_OK) + { + TABLE_LIST *save_next_global= table->next_global; + table->next_global= 0; + read_statistics_for_tables(thd, table, true /* force_reload */); + table->next_global= save_next_global; + } + } + /* Error path, a admin command failed. */ + if (thd->transaction_rollback_request || fatal_error) + { + /* + Unlikely, but transaction rollback was requested by one of storage + engines (e.g. due to deadlock). Perform it. + */ + if (trans_rollback_stmt(thd) || trans_rollback_implicit(thd)) + goto err; + } + else + { + if (trans_commit_stmt(thd)) + goto err; + is_table_modified= true; + } + close_thread_tables(thd); + + if (storage_engine_name[0]) + { + /* Table was changed (repair, optimize or something similar) */ + backup_log_info ddl_log; + bzero(&ddl_log, sizeof(ddl_log)); + lex_string_set(&ddl_log.org_storage_engine_name, + storage_engine_name); + ddl_log.query= *operator_name; + ddl_log.org_partitioned= storage_engine_partitioned; + ddl_log.org_database= table->db; + ddl_log.org_table= table->table_name; + ddl_log.org_table_id= tabledef_version; + backup_log_ddl(&ddl_log); + } + + thd->release_transactional_locks(); + + /* + If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run + separate open_tables() for each CHECK TABLE argument. + Right now we do not have a separate method to reset the prelocking + state in the lex to the state after parsing, so each open will pollute + this state: add elements to lex->srotuines_list, TABLE_LISTs to + lex->query_tables. Below is a lame attempt to recover from this + pollution. + @todo: have a method to reset a prelocking context, or use separate + contexts for each open. + */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; + + if (protocol->write()) + goto err; + DEBUG_SYNC(thd, "admin_command_kill_after_modify"); + } + thd->resume_subsequent_commits(suspended_wfc); + DBUG_EXECUTE_IF("inject_analyze_table_sleep", my_sleep(500000);); + if (is_table_modified && is_cmd_replicated && + (!opt_readonly || thd->slave_thread) && !thd->lex->no_write_to_binlog) + { + thd->get_stmt_da()->set_overwrite_status(true); + auto res= write_bin_log(thd, true, thd->query(), thd->query_length()); + thd->get_stmt_da()->set_overwrite_status(false); + if (res) + goto err; + } + my_eof(thd); + + DBUG_RETURN(FALSE); + +err: + /* Make sure this table instance is not reused after the failure. */ + trans_rollback_stmt(thd); + if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END)) + trans_rollback(thd); + if (table && table->table) + { + table->table->mark_table_for_reopen(); + table->table= 0; + } + close_thread_tables(thd); // Shouldn't be needed + thd->release_transactional_locks(); + thd->resume_subsequent_commits(suspended_wfc); + DBUG_RETURN(TRUE); +} + + +/* + Assigned specified indexes for a table into key cache + + SYNOPSIS + mysql_assign_to_keycache() + thd Thread object + tables Table list (one table only) + + RETURN VALUES + FALSE ok + TRUE error +*/ + +bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* tables, + const LEX_CSTRING *key_cache_name) +{ + HA_CHECK_OPT check_opt; + KEY_CACHE *key_cache; + DBUG_ENTER("mysql_assign_to_keycache"); + + THD_STAGE_INFO(thd, stage_finding_key_cache); + check_opt.init(); + mysql_mutex_lock(&LOCK_global_system_variables); + if (!(key_cache= get_key_cache(key_cache_name))) + { + mysql_mutex_unlock(&LOCK_global_system_variables); + my_error(ER_UNKNOWN_KEY_CACHE, MYF(0), key_cache_name->str); + DBUG_RETURN(TRUE); + } + mysql_mutex_unlock(&LOCK_global_system_variables); + if (!key_cache->key_cache_inited) + { + my_error(ER_UNKNOWN_KEY_CACHE, MYF(0), key_cache_name->str); + DBUG_RETURN(true); + } + check_opt.key_cache= key_cache; + DBUG_RETURN(mysql_admin_table(thd, tables, &check_opt, + &msg_assign_to_keycache, TL_READ_NO_INSERT, 0, + 0, 0, 0, + &handler::assign_to_keycache, 0, false)); +} + + +/* + Preload specified indexes for a table into key cache + + SYNOPSIS + mysql_preload_keys() + thd Thread object + tables Table list (one table only) + + RETURN VALUES + FALSE ok + TRUE error +*/ + +bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) +{ + DBUG_ENTER("mysql_preload_keys"); + /* + We cannot allow concurrent inserts. The storage engine reads + directly from the index file, bypassing the cache. It could read + outdated information if parallel inserts into cache blocks happen. + */ + DBUG_RETURN(mysql_admin_table(thd, tables, 0, + &msg_preload_keys, TL_READ_NO_INSERT, + 0, 0, 0, 0, + &handler::preload_keys, 0, false)); +} + + +bool Sql_cmd_analyze_table::execute(THD *thd) +{ + LEX *m_lex= thd->lex; + TABLE_LIST *first_table= m_lex->first_select_lex()->table_list.first; + bool res= TRUE; + thr_lock_type lock_type = TL_READ_NO_INSERT; + DBUG_ENTER("Sql_cmd_analyze_table::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; + if (thd->has_read_only_protection()) + goto error; + + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, + &msg_analyze, lock_type, 1, 0, 0, 0, + &handler::ha_analyze, 0, true); + m_lex->first_select_lex()->table_list.first= first_table; + m_lex->query_tables= first_table; + +#ifdef WITH_WSREP + wsrep_error_label: +#endif /* WITH_WSREP */ +error: + DBUG_RETURN(res); +} + + +bool Sql_cmd_check_table::execute(THD *thd) +{ + LEX *m_lex= thd->lex; + TABLE_LIST *first_table= m_lex->first_select_lex()->table_list.first; + thr_lock_type lock_type = TL_READ_NO_INSERT; + bool res= TRUE; + DBUG_ENTER("Sql_cmd_check_table::execute"); + + if (check_table_access(thd, SELECT_ACL, first_table, + TRUE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, &msg_check, + lock_type, 0, 0, HA_OPEN_FOR_REPAIR, 0, + &handler::ha_check, &view_check, false); + + m_lex->first_select_lex()->table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} + + +bool Sql_cmd_optimize_table::execute(THD *thd) +{ + LEX *m_lex= thd->lex; + TABLE_LIST *first_table= m_lex->first_select_lex()->table_list.first; + bool res= TRUE; + Recreate_info recreate_info; + DBUG_ENTER("Sql_cmd_optimize_table::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); + res= (specialflag & SPECIAL_NO_NEW_FUNC) ? + mysql_recreate_table(thd, first_table, &recreate_info, true) : + mysql_admin_table(thd, first_table, &m_lex->check_opt, + &msg_optimize, TL_WRITE, 1, 0, 0, 0, + &handler::ha_optimize, 0, true); + m_lex->first_select_lex()->table_list.first= first_table; + m_lex->query_tables= first_table; + +#ifdef WITH_WSREP +wsrep_error_label: +#endif /* WITH_WSREP */ +error: + DBUG_RETURN(res); +} + + +bool Sql_cmd_repair_table::execute(THD *thd) +{ + LEX *m_lex= thd->lex; + TABLE_LIST *first_table= m_lex->first_select_lex()->table_list.first; + bool res= TRUE; + DBUG_ENTER("Sql_cmd_repair_table::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, &msg_repair, + TL_WRITE, 1, + MY_TEST(m_lex->check_opt.sql_flags & TT_USEFRM), + HA_OPEN_FOR_REPAIR, &prepare_for_repair, + &handler::ha_repair, &view_repair, true); + + m_lex->first_select_lex()->table_list.first= first_table; + m_lex->query_tables= first_table; + +#ifdef WITH_WSREP +wsrep_error_label: +#endif /* WITH_WSREP */ +error: + DBUG_RETURN(res); +} |