1074 lines
32 KiB
C++
1074 lines
32 KiB
C++
/*
|
|
Copyright (c) 2006, 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 <string>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <iostream>
|
|
#include "sql_string.h"
|
|
#include "mysqld_error.h"
|
|
#include "my_default.h"
|
|
#include "check/mysqlcheck.h"
|
|
#include "../scripts/mysql_fix_privilege_tables_sql.c"
|
|
#include "../scripts/sql_commands_sys_schema.h"
|
|
|
|
#include "base/abstract_connection_program.h"
|
|
#include "base/abstract_options_provider.h"
|
|
#include "base/show_variable_query_extractor.h"
|
|
#include "base/mysql_query_runner.h"
|
|
|
|
using std::string;
|
|
using std::vector;
|
|
using namespace Mysql::Tools::Base;
|
|
using std::stringstream;
|
|
|
|
int mysql_check_errors;
|
|
|
|
const int SYS_TABLE_COUNT = 1;
|
|
const int SYS_VIEW_COUNT = 100;
|
|
const int SYS_TRIGGER_COUNT = 2;
|
|
const int SYS_FUNCTION_COUNT = 22;
|
|
const int SYS_PROCEDURE_COUNT = 26;
|
|
|
|
/**
|
|
Error callback to be called from mysql_check functionality.
|
|
*/
|
|
static void mysql_check_error_callback(MYSQL *mysql, string when)
|
|
{
|
|
mysql_check_errors= 1;
|
|
}
|
|
|
|
const char *load_default_groups[]=
|
|
{
|
|
"client", /* Read settings how to connect to server */
|
|
"mysql_upgrade", /* Read special settings for mysql_upgrade*/
|
|
0
|
|
};
|
|
|
|
namespace Mysql{
|
|
namespace Tools{
|
|
namespace Upgrade{
|
|
|
|
using std::vector;
|
|
using std::string;
|
|
using std::stringstream;
|
|
|
|
enum exit_codes
|
|
{
|
|
EXIT_INIT_ERROR = 1,
|
|
EXIT_ALREADY_UPGRADED = 2,
|
|
EXIT_BAD_VERSION = 3,
|
|
EXIT_MYSQL_CHECK_ERROR = 4,
|
|
EXIT_UPGRADING_QUERIES_ERROR = 5,
|
|
};
|
|
|
|
|
|
class Mysql_connection_holder
|
|
{
|
|
MYSQL* m_mysql_connection;
|
|
|
|
public:
|
|
explicit Mysql_connection_holder(MYSQL *mysql_connection)
|
|
: m_mysql_connection(mysql_connection)
|
|
{ }
|
|
|
|
~Mysql_connection_holder()
|
|
{
|
|
mysql_close(this->m_mysql_connection);
|
|
}
|
|
};
|
|
|
|
|
|
class Program : public Base::Abstract_connection_program
|
|
{
|
|
public:
|
|
Program()
|
|
: Abstract_connection_program(),
|
|
m_mysql_connection(NULL),
|
|
m_temporary_verbose(false)
|
|
{}
|
|
|
|
string get_version()
|
|
{
|
|
return "2.0";
|
|
}
|
|
|
|
int get_first_release_year()
|
|
{
|
|
return 2000;
|
|
}
|
|
|
|
string get_description()
|
|
{
|
|
return "MySQL utility for upgrading databases to new MySQL versions.";
|
|
}
|
|
|
|
void short_usage()
|
|
{
|
|
std::cout << "Usage: " << get_name() <<" [OPTIONS]" << std::endl;
|
|
}
|
|
|
|
int get_error_code()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
@param query Query to execute
|
|
@return -1 if error, 1 if equal to value, 0 if different from value
|
|
*/
|
|
int execute_conditional_query(const char* query, const char* value_to_compare)
|
|
{
|
|
int condition_result= -1;
|
|
if (!mysql_query(this->m_mysql_connection, query))
|
|
{
|
|
MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
|
|
if (result)
|
|
{
|
|
MYSQL_ROW row;
|
|
if ((row = mysql_fetch_row(result)))
|
|
{
|
|
condition_result= (strcmp(row[0], value_to_compare) == 0);
|
|
}
|
|
mysql_free_result(result);
|
|
}
|
|
}
|
|
return condition_result;
|
|
}
|
|
|
|
/**
|
|
@return -1 if error, 1 if user is not there, 0 if it is
|
|
*/
|
|
int check_session_user_absence()
|
|
{
|
|
int no_session_user =
|
|
execute_conditional_query(
|
|
"SELECT COUNT(*) FROM mysql.user WHERE user = 'mysql.session'",
|
|
"0");
|
|
return no_session_user;
|
|
}
|
|
|
|
/**
|
|
@return -1 if error, 1 if the user is correctly configured, 0 if not
|
|
*/
|
|
int check_session_user_configuration()
|
|
{
|
|
int is_user_configured = 0;
|
|
is_user_configured =
|
|
execute_conditional_query(
|
|
"SELECT SUM(count)=3 FROM ( "
|
|
"SELECT COUNT(*) as count FROM mysql.tables_priv WHERE "
|
|
"Table_priv='Select' and User='mysql.session' and Db='mysql' and Table_name='user' "
|
|
"UNION ALL "
|
|
"SELECT COUNT(*) as count FROM mysql.db WHERE "
|
|
"Select_priv='Y' and User='mysql.session' and Db='performance_schema' "
|
|
"UNION ALL "
|
|
"SELECT COUNT(*) as count FROM mysql.user WHERE "
|
|
"Super_priv='Y' and User='mysql.session') as user_priv;",
|
|
"1");
|
|
return is_user_configured;
|
|
}
|
|
/**
|
|
Error codes:
|
|
EXIT_INIT_ERROR - Initialization error.
|
|
EXIT_ALREADY_UPGRADED - Server already upgraded.
|
|
EXIT_BAD_VERSION - Bad server version.
|
|
EXIT_MYSQL_CHECK_ERROR - Error during calling mysql_check functionality.
|
|
EXIT_UPGRADING_QUERIES_ERROR - Error during execution of upgrading queries.
|
|
*/
|
|
int execute(vector<string> positional_options)
|
|
{
|
|
/*
|
|
Disables output buffering to make printing to stdout and stderr order
|
|
deterministic.
|
|
*/
|
|
setbuf(stdout, NULL);
|
|
|
|
this->m_mysql_connection= this->create_connection();
|
|
// Remember to call mysql_close()
|
|
Mysql_connection_holder connection_holder(m_mysql_connection);
|
|
this->m_query_runner= new Mysql_query_runner(this->m_mysql_connection);
|
|
this->m_query_runner->add_message_callback(new Instance_callback
|
|
<int64, const Message_data&, Program>(this, &Program::process_error));
|
|
|
|
/*
|
|
Master and slave should be upgraded separately. All statements executed
|
|
by mysql_upgrade will not be binlogged.
|
|
'SET SQL_LOG_BIN=0' is executed before any other statements.
|
|
*/
|
|
if (this->m_upgrade_systables_only)
|
|
{
|
|
printf("The --upgrade-system-tables option was used, databases won't be "
|
|
"touched.\n");
|
|
}
|
|
if (!this->m_write_binlog)
|
|
{
|
|
if (mysql_query(this->m_mysql_connection, "SET SQL_LOG_BIN=0") != 0)
|
|
{
|
|
return this->print_error(1, "Cannot setup server variables.");
|
|
}
|
|
}
|
|
|
|
if (mysql_query(this->m_mysql_connection, "USE mysql") != 0)
|
|
{
|
|
return this->print_error(1, "Cannot select database.");
|
|
}
|
|
|
|
/*
|
|
Read the mysql_upgrade_info file to check if mysql_upgrade
|
|
already has been run for this installation of MySQL
|
|
*/
|
|
if (this->m_ignore_errors == false && this->is_upgrade_already_done())
|
|
{
|
|
printf("This installation of MySQL is already upgraded to %s, "
|
|
"use --force if you still need to run mysql_upgrade\n",
|
|
MYSQL_SERVER_VERSION);
|
|
return EXIT_ALREADY_UPGRADED;
|
|
}
|
|
|
|
if (this->m_check_version && this->is_version_matching() == false)
|
|
return EXIT_BAD_VERSION;
|
|
|
|
/*
|
|
Check and see if the Server Session Service default user exists
|
|
*/
|
|
|
|
int user_is_not_there= check_session_user_absence();
|
|
int is_user_correctly_configured= 1;
|
|
|
|
if(!user_is_not_there)
|
|
{
|
|
is_user_correctly_configured= check_session_user_configuration();
|
|
}
|
|
|
|
if(!is_user_correctly_configured)
|
|
{
|
|
return this->print_error(
|
|
EXIT_UPGRADING_QUERIES_ERROR,
|
|
"The mysql.session exists but is not correctly configured."
|
|
" The mysql.session needs SELECT privileges in the"
|
|
" performance_schema database and the mysql.db table and also"
|
|
" SUPER privileges.");
|
|
}
|
|
|
|
if (user_is_not_there < 0 || is_user_correctly_configured < 0)
|
|
{
|
|
return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
|
|
"Query against mysql.user table failed "
|
|
"when checking the mysql.session.");
|
|
}
|
|
|
|
/*
|
|
Run "mysql_fix_privilege_tables.sql" and "mysqlcheck".
|
|
|
|
First, upgrade all tables in the system database and then check
|
|
them.
|
|
|
|
The order is important here because we might encounter really old
|
|
log tables in CSV engine which have NULLable columns and old TIMESTAMPs.
|
|
Trying to do REPAIR TABLE on such table prior to upgrading it will fail,
|
|
because REPAIR will detect old TIMESTAMPs and try to upgrade them to
|
|
the new ones. In the process it will attempt to create a table with
|
|
NULLable columns which is not supported by CSV engine nowadays.
|
|
|
|
After that, run mysqlcheck on all tables.
|
|
*/
|
|
if (this->run_sql_fix_privilege_tables() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
|
|
if (this->m_upgrade_systables_only == false)
|
|
{
|
|
this->print_verbose_message("Checking system database.");
|
|
|
|
if (this->run_mysqlcheck_mysql_db_fixnames() != 0)
|
|
{
|
|
return this->print_error(EXIT_MYSQL_CHECK_ERROR, "Error during call to mysql_check.");
|
|
}
|
|
if (this->run_mysqlcheck_mysql_db_upgrade() != 0)
|
|
{
|
|
return this->print_error(EXIT_MYSQL_CHECK_ERROR, "Error during call to mysql_check.");
|
|
}
|
|
}
|
|
|
|
if (this->m_skip_sys_schema == false)
|
|
{
|
|
/*
|
|
If the sys schema does not exist, then create it
|
|
Otherwise, try to select from sys.version, if this does not
|
|
exist but the schema does, then raise an error rather than
|
|
overwriting/adding to the existing schema
|
|
*/
|
|
if (mysql_query(this->m_mysql_connection, "USE sys") != 0)
|
|
{
|
|
if (this->run_sys_schema_upgrade() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
} else {
|
|
/* If the database is empty, upgrade */
|
|
if (!mysql_query(this->m_mysql_connection,
|
|
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'sys'"))
|
|
{
|
|
MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
|
|
if (result)
|
|
{
|
|
MYSQL_ROW row;
|
|
if ((row = mysql_fetch_row(result)))
|
|
{
|
|
if (strcmp(row[0], "0") == 0)
|
|
{
|
|
// The sys database contained nothing
|
|
stringstream ss;
|
|
ss << "Found empty sys database. Installing the sys schema.";
|
|
this->print_verbose_message(ss.str());
|
|
if (this->run_sys_schema_upgrade() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
}
|
|
}
|
|
mysql_free_result(result);
|
|
}
|
|
}
|
|
|
|
/* If the version is smaller, upgrade */
|
|
if (mysql_query(this->m_mysql_connection, "SELECT * FROM sys.version") != 0)
|
|
{
|
|
return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
|
|
"A sys schema exists with no sys.version view. "
|
|
"If you have a user created sys schema, this must be "
|
|
"renamed for the upgrade to succeed.");
|
|
} else {
|
|
MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
|
|
if (result)
|
|
{
|
|
MYSQL_ROW row;
|
|
|
|
while ((row = mysql_fetch_row(result)))
|
|
{
|
|
stringstream ss;
|
|
ulong installed_sys_version = calc_server_version(row[0]);
|
|
ulong expected_sys_version = calc_server_version(SYS_SCHEMA_VERSION);
|
|
if (installed_sys_version >= expected_sys_version)
|
|
{
|
|
ss << "The sys schema is already up to date (version " << row[0] << ").";
|
|
this->print_verbose_message(ss.str());
|
|
} else {
|
|
ss << "Found outdated sys schema version " << row[0] << ".";
|
|
this->print_verbose_message(ss.str());
|
|
if (this->run_sys_schema_upgrade() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
}
|
|
}
|
|
mysql_free_result(result);
|
|
} else {
|
|
return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
|
|
"A sys schema exists with a sys.version view, but it returns no results.");
|
|
}
|
|
}
|
|
/*
|
|
The version may be the same, but in some upgrade scenarios
|
|
such as importing a 5.6 dump in to a fresh 5.7 install that
|
|
includes the mysql schema, and then running mysql_upgrade,
|
|
the functions/procedures will be removed.
|
|
|
|
In this case, we check for the expected counts of objects,
|
|
and if those do not match, we just re-install the schema.
|
|
*/
|
|
if (mysql_query(this->m_mysql_connection,
|
|
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'sys' AND TABLE_TYPE = 'BASE TABLE'") != 0)
|
|
{
|
|
return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
|
|
"Query against INFORMATION_SCHEMA.TABLES failed when checking the sys schema.");
|
|
} else {
|
|
MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
|
|
if (result)
|
|
{
|
|
MYSQL_ROW row;
|
|
|
|
while ((row = mysql_fetch_row(result)))
|
|
{
|
|
if (SYS_TABLE_COUNT > atoi(row[0]))
|
|
{
|
|
stringstream ss;
|
|
ss << "Found " << row[0] << " sys tables, but expected " << SYS_TABLE_COUNT << "."
|
|
" Re-installing the sys schema.";
|
|
this->print_verbose_message(ss.str());
|
|
if (this->run_sys_schema_upgrade() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
mysql_free_result(result);
|
|
}
|
|
}
|
|
|
|
if (mysql_query(this->m_mysql_connection,
|
|
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'sys' AND TABLE_TYPE = 'VIEW'") != 0)
|
|
{
|
|
return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
|
|
"Query against INFORMATION_SCHEMA.TABLES failed when checking the sys schema.");
|
|
} else {
|
|
MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
|
|
if (result)
|
|
{
|
|
MYSQL_ROW row;
|
|
|
|
while ((row = mysql_fetch_row(result)))
|
|
{
|
|
if (SYS_VIEW_COUNT > atoi(row[0]))
|
|
{
|
|
stringstream ss;
|
|
ss << "Found " << row[0] << " sys views, but expected " << SYS_VIEW_COUNT << "."
|
|
" Re-installing the sys schema.";
|
|
this->print_verbose_message(ss.str());
|
|
if (this->run_sys_schema_upgrade() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
mysql_free_result(result);
|
|
}
|
|
}
|
|
|
|
if (mysql_query(this->m_mysql_connection,
|
|
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = 'sys'") != 0)
|
|
{
|
|
return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
|
|
"Query against INFORMATION_SCHEMA.TRIGGERS failed when checking the sys schema.");
|
|
} else {
|
|
MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
|
|
if (result)
|
|
{
|
|
MYSQL_ROW row;
|
|
|
|
while ((row = mysql_fetch_row(result)))
|
|
{
|
|
if (SYS_TRIGGER_COUNT > atoi(row[0]))
|
|
{
|
|
stringstream ss;
|
|
ss << "Found " << row[0] << " sys triggers, but expected " << SYS_TRIGGER_COUNT << "."
|
|
" Re-installing the sys schema.";
|
|
this->print_verbose_message(ss.str());
|
|
if (this->run_sys_schema_upgrade() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
mysql_free_result(result);
|
|
}
|
|
}
|
|
|
|
if (mysql_query(this->m_mysql_connection,
|
|
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'sys' AND ROUTINE_TYPE = 'FUNCTION'") != 0)
|
|
{
|
|
return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
|
|
"Query against INFORMATION_SCHEMA.ROUTINES failed when checking the sys schema.");
|
|
} else {
|
|
MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
|
|
if (result)
|
|
{
|
|
MYSQL_ROW row;
|
|
|
|
while ((row = mysql_fetch_row(result)))
|
|
{
|
|
if (SYS_FUNCTION_COUNT > atoi(row[0]))
|
|
{
|
|
stringstream ss;
|
|
ss << "Found " << row[0] << " sys functions, but expected " << SYS_FUNCTION_COUNT << "."
|
|
" Re-installing the sys schema.";
|
|
this->print_verbose_message(ss.str());
|
|
if (this->run_sys_schema_upgrade() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
mysql_free_result(result);
|
|
}
|
|
}
|
|
|
|
if (mysql_query(this->m_mysql_connection,
|
|
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'sys' AND ROUTINE_TYPE = 'PROCEDURE'") != 0)
|
|
{
|
|
return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
|
|
"Query against INFORMATION_SCHEMA.ROUTINES failed when checking the sys schema.");
|
|
} else {
|
|
MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
|
|
if (result)
|
|
{
|
|
MYSQL_ROW row;
|
|
|
|
while ((row = mysql_fetch_row(result)))
|
|
{
|
|
if (SYS_PROCEDURE_COUNT > atoi(row[0]))
|
|
{
|
|
stringstream ss;
|
|
ss << "Found " << row[0] << " sys procedures, but expected " << SYS_PROCEDURE_COUNT << "."
|
|
" Re-installing the sys schema.";
|
|
this->print_verbose_message(ss.str());
|
|
if (this->run_sys_schema_upgrade() != 0)
|
|
{
|
|
return EXIT_UPGRADING_QUERIES_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
mysql_free_result(result);
|
|
}
|
|
}
|
|
|
|
}
|
|
if (mysql_query(this->m_mysql_connection, "USE mysql") != 0)
|
|
{
|
|
return this->print_error(1, "Cannot select mysql database.");
|
|
}
|
|
} else {
|
|
this->print_verbose_message("Skipping installation/upgrade of the sys schema.");
|
|
}
|
|
|
|
if (!this->m_upgrade_systables_only)
|
|
{
|
|
this->print_verbose_message("Checking databases.");
|
|
|
|
if (this->run_mysqlcheck_fixnames() != 0)
|
|
{
|
|
return this->print_error(EXIT_MYSQL_CHECK_ERROR, "Error during call to mysql_check.");
|
|
}
|
|
if (this->run_mysqlcheck_upgrade() != 0)
|
|
{
|
|
return this->print_error(EXIT_MYSQL_CHECK_ERROR, "Error during call to mysql_check.");
|
|
}
|
|
}
|
|
|
|
this->print_verbose_message("Upgrade process completed successfully.");
|
|
|
|
/* Create a file indicating upgrade has been performed */
|
|
this->create_mysql_upgrade_info_file();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void create_options()
|
|
{
|
|
this->create_new_option(&this->m_check_version, "version-check",
|
|
"Run this program only if its \'server version\' "
|
|
"matches the version of the server to which it's connecting, "
|
|
"(enabled by default); use --skip-version-check to avoid this check. "
|
|
"Note: the \'server version\' of the program is the version of the "
|
|
"MySQL server with which it was built/distributed.")
|
|
->set_short_character('k')
|
|
->set_value(true);
|
|
|
|
this->create_new_option(&this->m_upgrade_systables_only, "upgrade-system-tables",
|
|
"Only upgrade the system tables, do not try to upgrade the data.")
|
|
->set_short_character('s');
|
|
|
|
this->create_new_option(&this->m_verbose, "verbose",
|
|
"Display more output about the process.")
|
|
->set_short_character('v')
|
|
->set_value(true);
|
|
|
|
this->create_new_option(&this->m_write_binlog, "write-binlog",
|
|
"Write all executed SQL statements to binary log. Disabled by default; "
|
|
"use when statements should be sent to replication slaves.");
|
|
|
|
this->create_new_option(&this->m_ignore_errors, "force",
|
|
"Force execution of SQL statements even if mysql_upgrade has already "
|
|
"been executed for the current version of MySQL.")
|
|
->set_short_character('f');
|
|
|
|
this->create_new_option(&this->m_skip_sys_schema, "skip-sys-schema",
|
|
"Do not upgrade/install the sys schema.")
|
|
->set_value(false);
|
|
}
|
|
|
|
void error(const Message_data& message)
|
|
{
|
|
std::cerr << "Upgrade process encountered error and will not continue."
|
|
<< std::endl;
|
|
|
|
exit(message.get_code());
|
|
}
|
|
|
|
private:
|
|
/**
|
|
Process messages and decides if to prints them.
|
|
*/
|
|
int64 process_error(const Message_data& message)
|
|
{
|
|
if (this->m_temporary_verbose
|
|
|| message.get_message_type() == Message_type_error)
|
|
{
|
|
message.print_error(this->get_name());
|
|
}
|
|
if (this->m_ignore_errors == false
|
|
&& message.get_message_type() == Message_type_error)
|
|
{
|
|
return message.get_code();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Process warning messages during upgrades.
|
|
*/
|
|
void process_warning(const Message_data& message)
|
|
{
|
|
if (this->m_temporary_verbose &&
|
|
message.get_message_type() == Message_type_warning)
|
|
{
|
|
message.print_error(this->get_name());
|
|
}
|
|
}
|
|
/**
|
|
Prints error occurred in main routine.
|
|
*/
|
|
int print_error(int exit_code, string message)
|
|
{
|
|
std::cout << "Error occurred: " << message << std::endl;
|
|
return exit_code;
|
|
}
|
|
|
|
/**
|
|
Update all system tables in MySQL Server to current
|
|
version executing all the SQL commands
|
|
compiled into the mysql_fix_privilege_tables array
|
|
*/
|
|
int64 run_sql_fix_privilege_tables()
|
|
{
|
|
const char **query_ptr;
|
|
int64 result;
|
|
|
|
Mysql_query_runner runner(*this->m_query_runner);
|
|
Instance_callback<int64, const Mysql_query_runner::Row&, Program>
|
|
result_cb(this, &Program::result_callback);
|
|
Instance_callback<int64, const Message_data&, Program>
|
|
message_cb(this, &Program::fix_privilage_tables_error);
|
|
|
|
runner.add_result_callback(&result_cb);
|
|
runner.add_message_callback(&message_cb);
|
|
|
|
this->print_verbose_message("Running queries to upgrade MySQL server.");
|
|
|
|
for ( query_ptr= &mysql_fix_privilege_tables[0];
|
|
*query_ptr != NULL;
|
|
query_ptr++
|
|
)
|
|
{
|
|
/*
|
|
Check if next query is SHOW WARNINGS, if so enable temporarily
|
|
verbosity of server messages.
|
|
*/
|
|
this->m_temporary_verbose= (*(query_ptr+1) != NULL
|
|
&& strcmp(*(query_ptr+1), "SHOW WARNINGS;\n") == 0);
|
|
|
|
result= runner.run_query(*query_ptr);
|
|
if (!this->m_ignore_errors && result != 0)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Update the sys schema
|
|
*/
|
|
int run_sys_schema_upgrade()
|
|
{
|
|
const char **query_ptr;
|
|
int result;
|
|
|
|
Mysql_query_runner runner(*this->m_query_runner);
|
|
Instance_callback<int64, const Mysql_query_runner::Row&, Program>
|
|
result_cb(this, &Program::result_callback);
|
|
Instance_callback<int64, const Message_data&, Program>
|
|
message_cb(this, &Program::fix_privilage_tables_error);
|
|
|
|
runner.add_result_callback(&result_cb);
|
|
runner.add_message_callback(&message_cb);
|
|
|
|
this->print_verbose_message("Upgrading the sys schema.");
|
|
|
|
for ( query_ptr= &mysql_sys_schema[0];
|
|
*query_ptr != NULL;
|
|
query_ptr++
|
|
)
|
|
{
|
|
result= runner.run_query(*query_ptr);
|
|
if (!this->m_ignore_errors && result != 0)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Gets path to file to write upgrade info into. Path is based on datadir of
|
|
server.
|
|
*/
|
|
int64 get_upgrade_info_file_name(char* name)
|
|
{
|
|
bool exists;
|
|
|
|
if (m_datadir.empty())
|
|
{
|
|
int64 res= Show_variable_query_extractor::get_variable_value(
|
|
this->m_query_runner, "datadir", m_datadir, exists);
|
|
|
|
res |= !exists;
|
|
if (res != 0)
|
|
{
|
|
return res;
|
|
}
|
|
}
|
|
|
|
fn_format(name, "mysql_upgrade_info", m_datadir.c_str(), "", MYF(0));
|
|
|
|
return 0;
|
|
}
|
|
/**
|
|
Read the content of mysql_upgrade_info file and
|
|
compare the version number form file against
|
|
version number which mysql_upgrade was compiled for.
|
|
|
|
NOTE
|
|
This is an optimization to avoid running mysql_upgrade
|
|
when it's already been performed for the particular
|
|
version of MySQL.
|
|
|
|
In case the MySQL server can't return the upgrade info
|
|
file it's always better to report that the upgrade hasn't
|
|
been performed.
|
|
*/
|
|
bool is_upgrade_already_done()
|
|
{
|
|
FILE *in;
|
|
char upgrade_info_file[FN_REFLEN]= {0};
|
|
char buf[sizeof(MYSQL_SERVER_VERSION)+1];
|
|
char *res;
|
|
|
|
this->print_verbose_message("Checking if update is needed.");
|
|
|
|
if (this->get_upgrade_info_file_name(upgrade_info_file) != 0)
|
|
return false; /* Could not get filename => not sure */
|
|
|
|
if (!(in= my_fopen(upgrade_info_file, O_RDONLY, MYF(0))))
|
|
return false; /* Could not open file => not sure */
|
|
|
|
/*
|
|
Read from file, don't care if it fails since it
|
|
will be detected by the strncmp
|
|
*/
|
|
memset(buf, 0, sizeof(buf));
|
|
res= fgets(buf, sizeof(buf), in);
|
|
|
|
my_fclose(in, MYF(0));
|
|
|
|
if (!res)
|
|
return false; /* Could not read from file => not sure */
|
|
|
|
return (strncmp(res, MYSQL_SERVER_VERSION,
|
|
sizeof(MYSQL_SERVER_VERSION)-1)==0);
|
|
}
|
|
|
|
/**
|
|
Write mysql_upgrade_info file in servers data dir indicating that
|
|
upgrade has been done for this version
|
|
|
|
NOTE
|
|
This might very well fail but since it's just an optimization
|
|
to run mysql_upgrade only when necessary the error can be
|
|
ignored.
|
|
*/
|
|
void create_mysql_upgrade_info_file()
|
|
{
|
|
FILE *out;
|
|
char upgrade_info_file[FN_REFLEN]= {0};
|
|
|
|
if (this->get_upgrade_info_file_name(upgrade_info_file) != 0)
|
|
return; /* Could not get filename => skip */
|
|
|
|
if (!(out= my_fopen(upgrade_info_file, O_TRUNC | O_WRONLY, MYF(0))))
|
|
{
|
|
fprintf(stderr,
|
|
"Could not create the upgrade info file '%s' in "
|
|
"the MySQL Servers datadir, errno: %d\n",
|
|
upgrade_info_file, errno);
|
|
return;
|
|
}
|
|
|
|
/* Write new version to file */
|
|
fputs(MYSQL_SERVER_VERSION, out);
|
|
my_fclose(out, MYF(0));
|
|
|
|
/*
|
|
Check if the upgrade_info_file was properly created/updated
|
|
It's not a fatal error -> just print a message if it fails.
|
|
*/
|
|
if (!this->is_upgrade_already_done())
|
|
fprintf(stderr,
|
|
"Could not write to the upgrade info file '%s' in "
|
|
"the MySQL Servers datadir, errno: %d\n",
|
|
upgrade_info_file, errno);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
Check if the server version matches with the server version mysql_upgrade
|
|
was compiled with.
|
|
|
|
@return true match successful
|
|
false failed
|
|
*/
|
|
bool is_version_matching()
|
|
{
|
|
string version;
|
|
bool exists;
|
|
|
|
this->print_verbose_message("Checking server version.");
|
|
|
|
if (Show_variable_query_extractor::get_variable_value(
|
|
this->m_query_runner, "version", version, exists) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (this->calc_server_version(version.c_str()) != MYSQL_VERSION_ID)
|
|
{
|
|
fprintf(stderr, "Error: Server version (%s) does not match with the "
|
|
"version of\nthe server (%s) with which this program was built/"
|
|
"distributed. You can\nuse --skip-version-check to skip this "
|
|
"check.\n", version.c_str(), MYSQL_SERVER_VERSION);
|
|
return false;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Convert the specified version string into the numeric format.
|
|
*/
|
|
ulong calc_server_version(const char *some_version)
|
|
{
|
|
uint major, minor, version;
|
|
const char *point= some_version, *end_point;
|
|
major= (uint) strtoul(point, (char**)&end_point, 10); point=end_point+1;
|
|
minor= (uint) strtoul(point, (char**)&end_point, 10); point=end_point+1;
|
|
version= (uint) strtoul(point, (char**)&end_point, 10);
|
|
return (ulong) major * 10000L + (ulong)(minor * 100 + version);
|
|
}
|
|
|
|
/**
|
|
Server message callback to be called during execution of upgrade queries.
|
|
*/
|
|
int64 fix_privilage_tables_error(const Message_data& message)
|
|
{
|
|
// This if it is error message and if it is not expected one.
|
|
if (message.get_message_type() == Message_type_error
|
|
&& is_expected_error(message.get_code()) == false)
|
|
{
|
|
// Pass this message to other callbacks, i.e. print_error to be printed out.
|
|
return 0;
|
|
}
|
|
process_warning(message);
|
|
// Do not pass filtered out messages to other callbacks, i.e. print_error.
|
|
return -1;
|
|
}
|
|
|
|
int64 result_callback(const Mysql_query_runner::Row& result_row)
|
|
{
|
|
/*
|
|
This is an old hacky way used in upgrade queries to show warnings from
|
|
executed queries in fix_privilege_tables. It is not result from
|
|
"SHOW WARNINGS" query.
|
|
*/
|
|
if (result_row.size() == 1 && !(result_row.is_value_null(0)))
|
|
{
|
|
String error;
|
|
uint dummy_errors;
|
|
error.copy("warning:", 8, &my_charset_latin1,
|
|
this->m_mysql_connection->charset, &dummy_errors);
|
|
std::string result= result_row[0];
|
|
result= result.substr(0, 8);
|
|
|
|
if (my_strcasecmp(this->m_mysql_connection->charset,
|
|
result.c_str(), error.c_ptr()) == 0)
|
|
{
|
|
std::cerr << result_row[0] << std::endl;
|
|
}
|
|
}
|
|
Mysql_query_runner::cleanup_result(result_row);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Checks if given error code is expected during upgrade queries execution.
|
|
*/
|
|
bool is_expected_error(int64 error_no)
|
|
{
|
|
static const int64 expected_errors[]=
|
|
{
|
|
ER_DUP_FIELDNAME, /* Duplicate column name */
|
|
ER_DUP_KEYNAME, /* Duplicate key name */
|
|
ER_BAD_FIELD_ERROR, /* Unknown column */
|
|
0
|
|
};
|
|
|
|
const int64* expected_error= expected_errors;
|
|
while (*expected_error)
|
|
{
|
|
if (*expected_error == error_no)
|
|
{
|
|
return true; /* Found expected error */
|
|
}
|
|
expected_error++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Prepares mysqlcheck program instance to be used by mysql_upgrade.
|
|
*/
|
|
Mysql::Tools::Check::Program* prepare_mysqlcheck(
|
|
Mysql::Tools::Check::Program& mysql_check)
|
|
{
|
|
mysql_check_errors= 0;
|
|
|
|
return (&mysql_check)
|
|
->set_ignore_errors(this->m_ignore_errors)
|
|
->enable_writing_binlog(this->m_write_binlog)
|
|
->enable_verbosity(this->m_verbose)
|
|
->set_error_callback(::mysql_check_error_callback);
|
|
}
|
|
|
|
/**
|
|
Check and upgrade(if necessary) all tables in the server using mysqlcheck.
|
|
*/
|
|
int run_mysqlcheck_upgrade()
|
|
{
|
|
Mysql::Tools::Check::Program mysql_check;
|
|
this->prepare_mysqlcheck(mysql_check)
|
|
->enable_auto_repair(true)
|
|
->enable_upgrade(true)
|
|
->set_skip_database("mysql")
|
|
->check_all_databases(this->m_mysql_connection);
|
|
return mysql_check_errors;
|
|
}
|
|
|
|
/**
|
|
Upgrade all tables and DBs names in the server using mysqlcheck.
|
|
*/
|
|
int run_mysqlcheck_fixnames()
|
|
{
|
|
Mysql::Tools::Check::Program mysql_check;
|
|
this->prepare_mysqlcheck(mysql_check)
|
|
->enable_fixing_db_names(true)
|
|
->enable_fixing_table_names(true)
|
|
->set_skip_database("mysql")
|
|
->upgrade_all_databases(this->m_mysql_connection);
|
|
return mysql_check_errors;
|
|
}
|
|
|
|
/**
|
|
Check and upgrade(if necessary) all system tables in the server using
|
|
mysqlcheck.
|
|
*/
|
|
int run_mysqlcheck_mysql_db_upgrade()
|
|
{
|
|
vector<string> databases;
|
|
Mysql::Tools::Check::Program mysql_check;
|
|
|
|
databases.push_back("mysql");
|
|
this->prepare_mysqlcheck(mysql_check)
|
|
->enable_auto_repair(true)
|
|
->enable_upgrade(true)
|
|
->check_databases(this->m_mysql_connection, databases);
|
|
return mysql_check_errors;
|
|
}
|
|
|
|
/**
|
|
Upgrade all system tables and system DB names in the server using
|
|
mysqlcheck.
|
|
*/
|
|
int run_mysqlcheck_mysql_db_fixnames()
|
|
{
|
|
vector<string> databases;
|
|
Mysql::Tools::Check::Program mysql_check;
|
|
|
|
databases.push_back("mysql");
|
|
this->prepare_mysqlcheck(mysql_check)
|
|
->enable_fixing_db_names(true)
|
|
->enable_fixing_table_names(true)
|
|
->upgrade_databases(this->m_mysql_connection, databases);
|
|
return mysql_check_errors;
|
|
}
|
|
|
|
void print_verbose_message(string message)
|
|
{
|
|
if (!this->m_verbose)
|
|
return;
|
|
|
|
std::cout << message << std::endl;
|
|
}
|
|
|
|
MYSQL* m_mysql_connection;
|
|
Mysql_query_runner* m_query_runner;
|
|
string m_datadir;
|
|
bool m_write_binlog;
|
|
bool m_upgrade_systables_only;
|
|
bool m_skip_sys_schema;
|
|
bool m_check_version;
|
|
bool m_ignore_errors;
|
|
bool m_verbose;
|
|
/**
|
|
Enabled during some queries execution to force printing all notes and
|
|
warnings regardless "verbose" option.
|
|
*/
|
|
bool m_temporary_verbose;
|
|
};
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
static ::Mysql::Tools::Upgrade::Program program;
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
program.run(argc, argv);
|
|
return 0;
|
|
}
|