mysql5/mysql-5.7.27/client/check/mysqlcheck_core.cc

700 lines
18 KiB
C++

/*
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 <m_ctype.h>
#include <mysql_version.h>
#include <mysqld_error.h>
#include <string>
#include <vector>
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<string> tables4repair, tables4rebuild, alter_table_cmds, failed_tables;
static int process_all_databases();
static int process_databases(vector<string> db_names);
static int process_selected_tables(string db, vector<string> 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<string> db_names)
{
int result = 0;
vector<string>::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<string> table_names)
{
if (use_db(db))
return 1;
vector<string>::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<string> 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<string> 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<string>::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<string> 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<string>());
}
int Program::upgrade_databases(MYSQL* connection, vector<string> 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<string>());
}
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<string> 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;
}