/* Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved. 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-1301 USA */ #include "client_priv.h" #include "my_default.h" #include "mysqlcheck.h" #include #include #include #include #include using namespace Mysql::Tools::Check; using std::string; using std::vector; /* ALTER instead of repair. */ #define MAX_ALTER_STR_SIZE 128 * 1024 #define KEY_PARTITIONING_CHANGED_STR "KEY () partitioning changed" static MYSQL *sock= 0; static my_bool opt_alldbs= 0, opt_check_only_changed= 0, opt_extended= 0, opt_databases= 0, opt_fast= 0, opt_medium_check = 0, opt_quick= 0, opt_all_in_1= 0, opt_silent= 0, opt_auto_repair= 0, ignore_errors= 0, opt_frm= 0, opt_fix_table_names= 0, opt_fix_db_names= 0, opt_upgrade= 0, opt_write_binlog= 1; static uint verbose = 0; static string opt_skip_database; int what_to_do = 0; void (*DBError)(MYSQL *mysql, string when); static int first_error = 0; vector tables4repair, tables4rebuild, alter_table_cmds, failed_tables; static int process_all_databases(); static int process_databases(vector db_names); static int process_selected_tables(string db, vector table_names); static int process_all_tables_in_db(string database); static int process_one_db(string database); static int use_db(string database); static int handle_request_for_tables(string tables); static void print_result(); static string escape_table_name(string src); static int process_all_databases() { MYSQL_ROW row; MYSQL_RES *tableres; int result = 0; if (mysql_query(sock, "SHOW DATABASES") || !(tableres = mysql_store_result(sock))) { my_printf_error(0, "Error: Couldn't execute 'SHOW DATABASES': %s", MYF(0), mysql_error(sock)); return 1; } while ((row = mysql_fetch_row(tableres))) { if (process_one_db(row[0])) result = 1; } mysql_free_result(tableres); return result; } /* process_all_databases */ static int process_databases(vector db_names) { int result = 0; vector::iterator it; for (it= db_names.begin() ; it != db_names.end(); it++) { if (process_one_db(*it)) result = 1; } return result; } /* process_databases */ static int process_selected_tables(string db, vector table_names) { if (use_db(db)) return 1; vector::iterator it; for (it= table_names.begin(); it != table_names.end(); it++) { if (what_to_do != DO_UPGRADE) *it= escape_table_name(*it); handle_request_for_tables(*it); } return 0; } /* process_selected_tables */ static inline void escape_str(string src, size_t start, size_t end, string &res) { res+= '`'; for (size_t i= start; i < end; i++) { switch (src[i]) { case '`': /* Escape backtick character. */ res+= '`'; /* Fall through. */ default: res+= src[i]; } } res+= '`'; } static string escape_table_name(string src) { string res= ""; escape_str(src, 0, src.length(), res); return res; } static string escape_db_table_name(string src, size_t dot_pos) { string res= ""; /* Escape database name. */ escape_str(src, 0, dot_pos - 1, res); /* Add a dot. */ res+= '.'; /* Escape table name. */ escape_str(src, dot_pos, src.length(), res); return res; } static int process_all_tables_in_db(string database) { MYSQL_RES *res= NULL; MYSQL_ROW row; uint num_columns; if (use_db(database)) return 1; if ((mysql_query(sock, "SHOW /*!50002 FULL*/ TABLES") && mysql_query(sock, "SHOW TABLES")) || !(res= mysql_store_result(sock))) { my_printf_error(0, "Error: Couldn't get table list for database %s: %s", MYF(0), database.c_str(), mysql_error(sock)); return 1; } num_columns= mysql_num_fields(res); vector table_names; while ((row = mysql_fetch_row(res))) { /* Skip views if we don't perform renaming. */ if ((what_to_do != DO_UPGRADE) && (num_columns == 2) && (strcmp(row[1], "VIEW") == 0)) continue; table_names.push_back(row[0]); } mysql_free_result(res); process_selected_tables(database, table_names); return 0; } /* process_all_tables_in_db */ static int run_query(string query) { if (mysql_query(sock, query.c_str())) { fprintf(stderr, "Failed to run query \"%s\"\n", query.c_str()); fprintf(stderr, "Error: %s\n", mysql_error(sock)); return 1; } return 0; } static int fix_table_storage_name(string name) { if (strncmp(name.c_str(), "#mysql50#", 9)) return 1; int rc= run_query("RENAME TABLE `" + name + "` TO `" + name.substr(9) + "`"); if (verbose) printf("%-50s %s\n", name.c_str(), rc ? "FAILED" : "OK"); return rc; } static int fix_database_storage_name(string name) { if (strncmp(name.c_str(), "#mysql50#", 9)) return 1; int rc= run_query("ALTER DATABASE `" + name + "` UPGRADE DATA DIRECTORY NAME"); if (verbose) printf("%-50s %s\n", name.c_str(), rc ? "FAILED" : "OK"); return rc; } static int rebuild_table(string name) { int rc= 0; string query= "ALTER TABLE " + name + " FORCE"; if (mysql_real_query(sock, query.c_str(), (ulong)query.length())) { fprintf(stderr, "Failed to %s\n", query.c_str()); fprintf(stderr, "Error: %s\n", mysql_error(sock)); rc= 1; } else printf("%s\nRunning : %s\nstatus : OK\n", name.c_str(), query.c_str()); return rc; } static int process_one_db(string database) { if (opt_skip_database.length() > 0 && opt_alldbs && database == opt_skip_database) return 0; if (what_to_do == DO_UPGRADE) { int rc= 0; if (opt_fix_db_names && !strncmp(database.c_str(),"#mysql50#", 9)) { rc= fix_database_storage_name(database); database= database.substr(9); } if (rc || !opt_fix_table_names) return rc; } return process_all_tables_in_db(database); } static int use_db(string database) { if (mysql_get_server_version(sock) >= FIRST_INFORMATION_SCHEMA_VERSION && !my_strcasecmp( &my_charset_latin1, database.c_str(), INFORMATION_SCHEMA_DB_NAME)) return 1; if (mysql_get_server_version(sock) >= FIRST_PERFORMANCE_SCHEMA_VERSION && !my_strcasecmp( &my_charset_latin1, database.c_str(), PERFORMANCE_SCHEMA_DB_NAME)) return 1; if (mysql_select_db(sock, database.c_str())) { DBError(sock, "when selecting the database"); return 1; } return 0; } /* use_db */ static int disable_binlog() { return run_query("SET SQL_LOG_BIN=0"); } static int handle_request_for_tables(string tables) { string operation, options; switch (what_to_do) { case DO_CHECK: operation = "CHECK"; if (opt_quick) options+= " QUICK"; if (opt_fast) options+= " FAST"; if (opt_medium_check) options+= " MEDIUM"; /* Default */ if (opt_extended) options+= " EXTENDED"; if (opt_check_only_changed) options+= " CHANGED"; if (opt_upgrade) options+= " FOR UPGRADE"; break; case DO_REPAIR: operation= (opt_write_binlog) ? "REPAIR" : "REPAIR NO_WRITE_TO_BINLOG"; if (opt_quick) options+= " QUICK"; if (opt_extended) options+= " EXTENDED"; if (opt_frm) options+= " USE_FRM"; break; case DO_ANALYZE: operation= (opt_write_binlog) ? "ANALYZE" : "ANALYZE NO_WRITE_TO_BINLOG"; break; case DO_OPTIMIZE: operation= (opt_write_binlog) ? "OPTIMIZE" : "OPTIMIZE NO_WRITE_TO_BINLOG"; break; case DO_UPGRADE: return fix_table_storage_name(tables); } string query= operation + " TABLE " + tables + " " + options; if (mysql_real_query(sock, query.c_str(), (ulong)query.length())) { DBError(sock, "when executing '" + operation + " TABLE ... " + options + "'"); return 1; } print_result(); return 0; } static void print_result() { MYSQL_RES *res; MYSQL_ROW row; char prev[NAME_LEN*3+2]; char prev_alter[MAX_ALTER_STR_SIZE]; uint i; size_t dot_pos; my_bool found_error=0, table_rebuild=0; res = mysql_use_result(sock); dot_pos= strlen(sock->db) + 1; prev[0] = '\0'; prev_alter[0]= 0; for (i = 0; (row = mysql_fetch_row(res)); i++) { int changed = strcmp(prev, row[0]); my_bool status = !strcmp(row[2], "status"); if (status) { /* if there was an error with the table, we have --auto-repair set, and this isn't a repair op, then add the table to the tables4repair list */ if (found_error && opt_auto_repair && what_to_do != DO_REPAIR && strcmp(row[3],"OK")) { if (table_rebuild) { if (prev_alter[0]) alter_table_cmds.push_back(prev_alter); else tables4rebuild.push_back(escape_db_table_name(prev, dot_pos)); } else { tables4repair.push_back(escape_db_table_name(prev, dot_pos)); } } found_error= 0; table_rebuild= 0; prev_alter[0]= '\0'; if (opt_silent) continue; } if (status && changed) printf("%-50s %s", row[0], row[3]); else if (!status && changed) { printf("%s\n%-9s: %s", row[0], row[2], row[3]); if (opt_auto_repair && strcmp(row[2],"note")) { const char *alter_txt= strstr(row[3], "ALTER TABLE"); found_error= 1; if (alter_txt) { table_rebuild= 1; const char *match_str; if (!strncmp(row[3], KEY_PARTITIONING_CHANGED_STR, strlen(KEY_PARTITIONING_CHANGED_STR))) { if (strstr(alter_txt, "PARTITION BY") && strlen(alter_txt) < MAX_ALTER_STR_SIZE) { strncpy(prev_alter, alter_txt, MAX_ALTER_STR_SIZE-1); prev_alter[MAX_ALTER_STR_SIZE-1]= 0; } else { printf("\nError: Alter command unknown or too long (%d >= %d), " "please investigate the above or dump/reload to fix it!" "\n", (int)strlen(alter_txt), MAX_ALTER_STR_SIZE); found_error= 0; table_rebuild= 0; prev_alter[0]= '\0'; failed_tables.push_back(row[0]); } } else if ((match_str= strstr(alter_txt, "` UPGRADE PARTITIONING")) && strlen(match_str) == 22) { strcpy(prev_alter, alter_txt); } } else { /* Search the error message specific to pre 5.0 decimal type. "REPAIR TABLE" should not be present in the error message and "dump/reload" should be present in the error message. In this case, do not add table to the repair list. */ const char *repair_txt= strstr(row[3], "REPAIR TABLE"); const char *dump_txt= strstr(row[3], "dump/reload table"); if (dump_txt && !repair_txt) { found_error= 0; table_rebuild= 0; prev_alter[0]= '\0'; failed_tables.push_back(row[0]); } } } } else printf("%-9s: %s", row[2], row[3]); my_stpcpy(prev, row[0]); putchar('\n'); } /* add the last table to be repaired to the list */ if (found_error && opt_auto_repair && what_to_do != DO_REPAIR) { if (table_rebuild) { if (prev_alter[0]) alter_table_cmds.push_back(prev_alter); else tables4rebuild.push_back(escape_db_table_name(prev, dot_pos)); } else { tables4repair.push_back(escape_db_table_name(prev, dot_pos)); } } mysql_free_result(res); } void Mysql::Tools::Check::mysql_check(MYSQL* connection, int what_to_do, my_bool opt_alldbs, my_bool opt_check_only_changed, my_bool opt_extended, my_bool opt_databases, my_bool opt_fast, my_bool opt_medium_check, my_bool opt_quick, my_bool opt_all_in_1, my_bool opt_silent, my_bool opt_auto_repair, my_bool ignore_errors, my_bool opt_frm, my_bool opt_fix_table_names, my_bool opt_fix_db_names, my_bool opt_upgrade, my_bool opt_write_binlog, uint verbose, string opt_skip_database, vector arguments, void (*dberror)(MYSQL *mysql, string when)) { ::sock= connection; ::what_to_do= what_to_do; ::opt_alldbs= opt_alldbs; ::opt_check_only_changed= opt_check_only_changed; ::opt_extended= opt_extended; ::opt_databases= opt_databases; ::opt_fast= opt_fast; ::opt_medium_check= opt_medium_check; ::opt_quick= opt_quick; ::opt_all_in_1= opt_all_in_1; ::opt_silent= opt_silent; ::opt_auto_repair= opt_auto_repair; ::ignore_errors= ignore_errors; ::opt_frm= opt_frm; ::opt_fix_table_names= opt_fix_table_names; ::opt_fix_db_names= opt_fix_db_names; ::opt_upgrade= opt_upgrade; ::opt_write_binlog= opt_write_binlog; ::verbose= verbose; ::opt_skip_database= opt_skip_database; ::DBError= dberror; if (!::opt_write_binlog) { if (disable_binlog()) { first_error= 1; return; } } if (::opt_alldbs) process_all_databases(); /* Only one database and selected table(s) */ else if (arguments.size() > 1 && !::opt_databases) { string db_name= arguments[0]; arguments.erase(arguments.begin()); process_selected_tables(db_name, arguments); } /* One or more databases, all tables */ else process_databases(arguments); if (::opt_auto_repair) { if (!::opt_silent) { if (!(tables4repair.empty() && tables4rebuild.empty())) puts("\nRepairing tables"); if (!(alter_table_cmds.empty())) puts("\nUpgrading tables"); } ::what_to_do = DO_REPAIR; vector::iterator it; for (it = tables4repair.begin(); it != tables4repair.end() ; it++) { handle_request_for_tables(*it); } for (it = tables4rebuild.begin(); it != tables4rebuild.end() ; it++) { rebuild_table(*it); } for (it = alter_table_cmds.begin(); it != alter_table_cmds.end() ; it++) { if (0 == run_query(*it)) printf("Running : %s\nstatus : OK\n", (*it).c_str()); else { fprintf(stderr, "Failed to %s\n", (*it).c_str()); fprintf(stderr, "Error: %s\n", mysql_error(sock)); } } if (!failed_tables.empty()) { fprintf(stderr, "These tables cannot be automatically upgraded," " see the log above:\n"); } for (it = failed_tables.begin(); it != failed_tables.end() ; it++) { fprintf(stderr, "%s\n", it->c_str()); } } } Program::Program() : m_what_to_do(0), m_auto_repair(false), m_upgrade(false), m_verbose(false), m_ignore_errors(false), m_write_binlog(false), m_process_all_dbs(false), m_fix_table_names(false), m_fix_db_names(false), m_connection(NULL), m_error_callback(NULL) { } int Program::check_databases(MYSQL* connection, vector databases) { this->m_connection= connection; this->m_process_all_dbs= false; return this->set_what_to_do(DO_CHECK) ->execute(databases); } int Program::check_all_databases(MYSQL* connection) { this->m_connection= connection; this->m_process_all_dbs= true; return this->set_what_to_do(DO_CHECK) ->execute(vector()); } int Program::upgrade_databases(MYSQL* connection, vector databases) { this->m_connection= connection; this->m_process_all_dbs= false; return this->set_what_to_do(DO_UPGRADE) ->execute(databases); } int Program::upgrade_all_databases(MYSQL* connection) { this->m_connection= connection; this->m_process_all_dbs= true; return this->set_what_to_do(DO_UPGRADE) ->execute(vector()); } Program* Program::enable_auto_repair(bool enable) { this->m_auto_repair= enable; return this; } Program* Program::enable_upgrade(bool enable) { this->m_upgrade= enable; return this; } Program* Program::enable_verbosity(bool enable) { this->m_verbose= enable; return this; } Program* Program::enable_writing_binlog(bool enable) { this->m_write_binlog= enable; return this; } Program* Program::enable_fixing_table_names(bool enable) { this->m_fix_table_names= enable; return this; } Program* Program::enable_fixing_db_names(bool enable) { this->m_fix_db_names= enable; return this; } Program* Program::set_ignore_errors(bool ignore) { this->m_ignore_errors= ignore; return this; } Program* Program::set_skip_database(string database) { this->m_database_to_skip= database; return this; } Program* Program::set_error_callback(void ( *error_callback)(MYSQL *mysql, string when)) { this->m_error_callback= error_callback; return this; } Program* Program::set_what_to_do(int functionality) { this->m_what_to_do= functionality; return this; } int Program::execute(vector positional_options) { Mysql::Tools::Check::mysql_check( this->m_connection, // connection this->m_what_to_do, // what_to_do this->m_process_all_dbs, // opt_alldbs false, // opt_check_only_changed false, // opt_extended !this->m_process_all_dbs, // opt_databases false, // opt_fast false, // opt_medium_check false, // opt_quick false, // opt_all_in_1 false, // opt_silent this->m_auto_repair, // opt_auto_repair this->m_ignore_errors, // ignore_errors false, // opt_frm this->m_fix_table_names, // opt_fix_table_names this->m_fix_db_names, // opt_fix_db_names this->m_upgrade, // opt_upgrade this->m_write_binlog, // opt_write_binlog this->m_verbose, // verbose this->m_database_to_skip, positional_options, this->m_error_callback); return 0; }