/* Copyright (c) 2015, 2018 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 "sql_formatter.h" #include "view.h" #include "mysql_function.h" #include "stored_procedure.h" #include "privilege.h" #include #include using namespace Mysql::Tools::Dump; void Sql_formatter::format_row_group(Row_group_dump_task* row_group) { std::size_t row_data_length = 0; // Calculate total length of data to be formatted. for (std::vector::iterator row_iterator= row_group->m_rows.begin(); row_iterator != row_group->m_rows.end(); ++row_iterator) { row_data_length+= 3; // Space for enclosing parentheses and comma. Row* row= *row_iterator; for (size_t column= row->m_row_data.size(); column-- > 0;) { // Space for escaped string, enclosing " and comma. row_data_length+= row->m_row_data.size_of_element(column) * 2 + 3; } } if (m_options->m_dump_column_names || row_group->m_has_generated_columns) { row_data_length+= 3; // Space for enclosing parentheses and space. const std::vector& fields= row_group->m_fields; for (std::vector::const_iterator field_iterator= fields.begin(); field_iterator != fields.end(); ++field_iterator) { row_data_length+= field_iterator->get_name().size() * 2 + 3; } } std::string row_string; /* Space for constant strings "INSERT INTO ... VALUES ()" with reserve for comments, modificators and future changes. */ const size_t INSERT_INTO_MAX_SIZE= 200; row_string.reserve(INSERT_INTO_MAX_SIZE + row_group->m_source_table->get_schema().size() + row_group->m_source_table->get_name().size() + row_data_length); if (m_options->m_insert_type_replace) row_string+= "REPLACE INTO "; else if (m_options->m_insert_type_ignore) row_string+= "INSERT IGNORE INTO "; else row_string+= "INSERT INTO "; row_string+= this->get_quoted_object_full_name(row_group->m_source_table); if (m_options->m_dump_column_names || row_group->m_has_generated_columns) { row_string+= " ("; const std::vector& fields= row_group->m_fields; for (std::vector::const_iterator field_iterator= fields.begin(); field_iterator != fields.end(); ++field_iterator) { if (field_iterator != fields.begin()) row_string+= ','; row_string+= this->quote_name(field_iterator->get_name()); } row_string+= ')'; } row_string+= " VALUES "; CHARSET_INFO* charset_info= this->get_charset(); std::vector is_blob; for (std::vector::const_iterator it= row_group->m_fields.begin(); it != row_group->m_fields.end(); ++it) { is_blob.push_back( it->get_character_set_nr() == my_charset_bin.number && (it->get_type() == MYSQL_TYPE_BIT || it->get_type() == MYSQL_TYPE_STRING || it->get_type() == MYSQL_TYPE_VAR_STRING || it->get_type() == MYSQL_TYPE_VARCHAR || it->get_type() == MYSQL_TYPE_BLOB || it->get_type() == MYSQL_TYPE_LONG_BLOB || it->get_type() == MYSQL_TYPE_MEDIUM_BLOB || it->get_type() == MYSQL_TYPE_TINY_BLOB || it->get_type() == MYSQL_TYPE_GEOMETRY)); } for (std::vector::const_iterator row_iterator= row_group->m_rows.begin(); row_iterator != row_group->m_rows.end(); ++row_iterator) { Row* row= *row_iterator; if (row_iterator != row_group->m_rows.begin()) row_string+= ','; row_string+= '('; size_t columns= row->m_row_data.size(); for (size_t column= 0; column < columns; ++column) { if (column > 0) row_string+= ','; size_t column_length; const char* column_data= row->m_row_data.get_buffer(column, column_length); if (row->m_row_data.is_value_null(column)) row_string+= "NULL"; else if (column_length == 0) row_string+= "''"; else if (row_group->m_fields[column].get_additional_flags() & NUM_FLAG) { if (column_length >= 1 && (my_isalpha(charset_info, column_data[0]) || (column_length >= 2 && column_data[0] == '-' && my_isalpha(charset_info, column_data[1])))) { row_string+= "NULL"; } else if (row_group->m_fields[column].get_type() == MYSQL_TYPE_DECIMAL) { row_string+= '\''; row_string.append(column_data, column_length); row_string+= '\''; } else row_string.append(column_data, column_length); } else if (m_options->m_hex_blob && is_blob[column]) { row_string+= "0x"; m_escaping_runner->append_hex_string( &row_string, column_data, column_length); } else { if (is_blob[column]) row_string += "_binary "; row_string+= '\"'; m_escaping_runner->append_escape_string( &row_string, column_data, column_length); row_string+= '\"'; } } row_string+= ')'; } row_string+= ";\n"; this->append_output(row_string); } void Sql_formatter::format_table_indexes( Table_deferred_indexes_dump_task* table_indexes_dump_task) { Table* table= table_indexes_dump_task->get_related_table(); if (m_options->m_deffer_table_indexes) { /* Tables can have indexes which can refer to columns from other tables (ex: foreign keys). In that case we need to emit 'USE db' statement as the referenced table may not have been created */ bool use_added= false; std::string alter_base_string= "ALTER TABLE " + this->get_quoted_object_full_name(table) + " ADD "; for (std::vector::const_iterator it= table->get_indexes_sql_definition().begin(); it != table->get_indexes_sql_definition().end(); ++it) { if (!use_added) { this->append_output("USE " + this->quote_name(table->get_schema()) + ";\n"); use_added= true; } this->append_output(alter_base_string + (*it) + ";\n"); } } if (m_options->m_add_locks) this->append_output("UNLOCK TABLES;\n"); } void Sql_formatter::format_table_definition( Table_definition_dump_task* table_definition_dump_task) { Table* table= table_definition_dump_task->get_related_table(); bool use_added= false; if (m_options->m_drop_table) this->append_output("DROP TABLE IF EXISTS " + this->get_quoted_object_full_name(table) + ";\n"); if (m_options->m_deffer_table_indexes == 0 && !use_added) { use_added= true; this->append_output("USE " + this->quote_name(table->get_schema()) + ";\n"); } if (!m_options->m_suppress_create_table) this->append_output((m_options->m_deffer_table_indexes ? table->get_sql_definition_without_indexes() : table->get_sql_formatted_definition()) + ";\n"); if (m_options->m_add_locks) this->append_output("LOCK TABLES " + this->get_quoted_object_full_name(table) + " WRITE;\n"); } void Sql_formatter::format_database_start( Database_start_dump_task* database_definition_dump_task) { Database* database= database_definition_dump_task ->get_related_database(); if (m_options->m_drop_database) this->append_output("DROP DATABASE IF EXISTS " + this->quote_name(database->get_name()) + ";\n"); if (!m_options->m_suppress_create_database) this->append_output(database->get_sql_formatted_definition() + ";\n"); } void Sql_formatter::format_dump_end(Dump_end_dump_task* dump_start_dump_task) { std::ostringstream out; std::time_t sys_time = boost::chrono::system_clock::to_time_t( boost::chrono::system_clock::now()); // Convert to calendar time. std::string time_string = std::ctime(&sys_time); boost::trim(time_string); if (m_options->m_timezone_consistent) out << "SET TIME_ZONE=@OLD_TIME_ZONE;\n"; if (m_options->m_charsets_consistent) out << "SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT;\n" "SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS;\n" "SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION;\n"; out << "SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;\n" "SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;\n" "SET SQL_MODE=@OLD_SQL_MODE;\n"; out << "-- Dump end time: " << time_string << "\n"; this->append_output(out.str()); } void Sql_formatter::format_dump_start( Dump_start_dump_task* dump_start_dump_task) { // Convert to system time. std::time_t sys_time = boost::chrono::system_clock::to_time_t( boost::chrono::system_clock::now()); // Convert to calendar time. std::string time_string = std::ctime(&sys_time); // Skip trailing newline boost::trim(time_string); std::ostringstream out; out << "-- Dump created by MySQL pump utility, version: " MYSQL_SERVER_VERSION ", " SYSTEM_TYPE " (" MACHINE_TYPE ")\n" << "-- Dump start time: " << time_string << "\n" << "-- Server version: " << this->get_server_version_string() << "\n\n" << "SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;\n" "SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, " "FOREIGN_KEY_CHECKS=0;\n" << "SET @OLD_SQL_MODE=@@SQL_MODE;\n" "SET SQL_MODE=\"NO_AUTO_VALUE_ON_ZERO\";\n"; /* disable binlog */ out << "SET @@SESSION.SQL_LOG_BIN= 0;\n"; if (m_options->m_timezone_consistent) out << "SET @OLD_TIME_ZONE=@@TIME_ZONE;\n" "SET TIME_ZONE='+00:00';\n"; if (m_options->m_charsets_consistent) out << "SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT;\n" "SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;\n" "SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION;\n" "SET NAMES " << this->get_charset()->csname << ";\n"; if (dump_start_dump_task->m_gtid_mode == "OFF" && *((ulong*)&m_options->m_gtid_purged) == ((ulong)GTID_PURGED_ON)) { m_options->m_mysql_chain_element_options->get_program()->error( Mysql::Tools::Base::Message_data(1, "Server has GTIDs disabled.\n", Mysql::Tools::Base::Message_type_error)); return; } if (dump_start_dump_task->m_gtid_mode != "OFF") { /* value for m_gtid_purged is set by typecasting its address to ulong* however below conditions fails if we do direct comparison without typecasting on solaris sparc. Guessing that this is due to differnt endianess. */ if (*((ulong*)&m_options->m_gtid_purged) == ((ulong)GTID_PURGED_ON) || *((ulong*)&m_options->m_gtid_purged) == ((ulong)GTID_PURGED_AUTO)) { if (!m_mysqldump_tool_options->m_dump_all_databases && *((ulong*)&m_options->m_gtid_purged) == ((ulong)GTID_PURGED_AUTO)) { m_options->m_mysql_chain_element_options->get_program()->error( Mysql::Tools::Base::Message_data(1, "A partial dump from a server that is using GTID-based replication " "requires the --set-gtid-purged=[ON|OFF] option to be specified. Use ON " "if the intention is to deploy a new replication slave using only some " "of the data from the dumped server. Use OFF if the intention is to " "repair a table by copying it within a topology, and use OFF if the " "intention is to copy a table between replication topologies that are " "disjoint and will remain so.\n", Mysql::Tools::Base::Message_type_error)); return; } std::string gtid_output("SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '"); gtid_output+= (dump_start_dump_task->m_gtid_executed + "';\n"); out << gtid_output; } } this->append_output(out.str()); } void Sql_formatter::format_plain_sql_object( Abstract_plain_sql_object_dump_task* plain_sql_dump_task) { View* new_view_task= dynamic_cast(plain_sql_dump_task); if (new_view_task != NULL) { /* DROP VIEW statement followed by CREATE VIEW must be written to output as an atomic operation, else there is a possibility of bug#21399236. It happens when we DROP VIEW v1, and it uses column from view v2, which might get dropped before creation of real v1 view, and thus result in error during restore. */ format_sql_objects_definer(plain_sql_dump_task, "VIEW"); this->append_output("DROP VIEW IF EXISTS " + this->get_quoted_object_full_name(new_view_task) + ";\n" + plain_sql_dump_task->get_sql_formatted_definition() + ";\n"); return; } Mysql_function* new_func_task= dynamic_cast(plain_sql_dump_task); if (new_func_task != NULL) format_sql_objects_definer(plain_sql_dump_task, "FUNCTION"); Stored_procedure* new_proc_task= dynamic_cast(plain_sql_dump_task); if (new_proc_task != NULL) format_sql_objects_definer(plain_sql_dump_task, "PROCEDURE"); Privilege* new_priv_task= dynamic_cast(plain_sql_dump_task); if (new_priv_task != NULL) { if (m_options->m_drop_user) this->append_output("DROP USER " + (dynamic_cast(new_priv_task))->get_name() + ";\n"); } this->append_output(plain_sql_dump_task->get_sql_formatted_definition() + ";\n"); } void Sql_formatter::format_sql_objects_definer( Abstract_plain_sql_object_dump_task* plain_sql_dump_task, std::string object_type) { if (m_options->m_skip_definer) { std::vector object_ddl_lines; std::string object_ddl(plain_sql_dump_task->get_sql_formatted_definition()); boost::split(object_ddl_lines, object_ddl, boost::is_any_of("\n"), boost::token_compress_on); std::string new_sql_stmt; bool is_replaced= FALSE; for (std::vector::iterator it= object_ddl_lines.begin(); it != object_ddl_lines.end(); ++it) { std::string object_sql(*it); size_t object_pos= object_sql.find(object_type); size_t definer_pos= object_sql.find("DEFINER"); if (object_pos != std::string::npos && definer_pos != std::string::npos && definer_pos <= object_pos && !is_replaced) { object_sql.replace(definer_pos, (object_pos-definer_pos), ""); new_sql_stmt+= object_sql + "\n"; is_replaced= TRUE; } else new_sql_stmt+= object_sql + "\n"; } plain_sql_dump_task->set_sql_formatted_definition(new_sql_stmt); } } void Sql_formatter::format_object(Item_processing_data* item_to_process) { this->object_processing_starts(item_to_process); // format_row_group is placed first, as it is most occurring task. if (this->try_process_task (item_to_process, &Sql_formatter::format_row_group) || this->try_process_task (item_to_process, &Sql_formatter::format_table_definition) || this->try_process_task (item_to_process, &Sql_formatter::format_table_indexes) || this->try_process_task (item_to_process, &Sql_formatter::format_dump_start) || this->try_process_task (item_to_process, &Sql_formatter::format_dump_end) || this->try_process_task (item_to_process, &Sql_formatter::format_database_start) /* Abstract_plain_sql_object_dump_task must be last, as so of above derive from it too. */ || this->try_process_task (item_to_process, &Sql_formatter::format_plain_sql_object)) { // Item was processed. No further action required. } this->object_processing_ends(item_to_process); return; } Sql_formatter::Sql_formatter(I_connection_provider* connection_provider, Mysql::I_callable* message_handler, Simple_id_generator* object_id_generator, const Mysqldump_tool_chain_maker_options* mysqldump_tool_options, const Sql_formatter_options* options) : Abstract_output_writer_wrapper(message_handler, object_id_generator), Abstract_mysql_chain_element_extension( connection_provider, message_handler, options->m_mysql_chain_element_options), m_mysqldump_tool_options(mysqldump_tool_options), m_options(options) { m_escaping_runner= this->get_runner(); } Sql_formatter::~Sql_formatter() { delete m_escaping_runner; }