1 /*
2   Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
3 
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2.0,
6   as published by the Free Software Foundation.
7 
8   This program is also distributed with certain software (including
9   but not limited to OpenSSL) that is licensed under separate terms,
10   as designated in a particular file or component or in included license
11   documentation.  The authors of MySQL hereby grant you an additional
12   permission to link the program and your derivative works with the
13   separately licensed software that they have included with MySQL.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License, version 2.0, for more details.
19 
20   You should have received a copy of the GNU General Public License
21   along with this program; if not, write to the Free Software
22   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 #include "client/dump/sql_formatter.h"
26 
27 #include "my_config.h"
28 
29 #include <sys/types.h>
30 #include <chrono>
31 #include <ctime>
32 #include <functional>
33 #include <sstream>
34 #include <string>
35 
36 #include "client/dump/column_statistic.h"
37 #include "client/dump/mysql_function.h"
38 #include "client/dump/privilege.h"
39 #include "client/dump/stored_procedure.h"
40 #include "client/dump/view.h"
41 #include "m_ctype.h"
42 
43 using namespace Mysql::Tools::Dump;
44 
format_row_group(Row_group_dump_task * row_group)45 void Sql_formatter::format_row_group(Row_group_dump_task *row_group) {
46   std::size_t row_data_length = 0;
47   // Calculate total length of data to be formatted.
48   for (std::vector<Row *>::iterator row_iterator = row_group->m_rows.begin();
49        row_iterator != row_group->m_rows.end(); ++row_iterator) {
50     row_data_length += 3;  // Space for enclosing parentheses and comma.
51 
52     Row *row = *row_iterator;
53     for (size_t column = row->m_row_data.size(); column-- > 0;) {
54       // Space for escaped string, enclosing " and comma.
55       row_data_length += row->m_row_data.size_of_element(column) * 2 + 3;
56     }
57   }
58   if (m_options->m_dump_column_names || row_group->m_has_generated_columns) {
59     row_data_length += 3;  // Space for enclosing parentheses and space.
60     const std::vector<Mysql_field> &fields = row_group->m_fields;
61     for (std::vector<Mysql_field>::const_iterator field_iterator =
62              fields.begin();
63          field_iterator != fields.end(); ++field_iterator) {
64       row_data_length += field_iterator->get_name().size() * 2 + 3;
65     }
66   }
67   std::string row_string;
68   /*
69     Space for constant strings "INSERT INTO ... VALUES ()" with
70     reserve for comments, modificators and future changes.
71     */
72   const size_t INSERT_INTO_MAX_SIZE = 200;
73 
74   row_string.reserve(
75       INSERT_INTO_MAX_SIZE + row_group->m_source_table->get_schema().size() +
76       row_group->m_source_table->get_name().size() + row_data_length);
77 
78   if (m_options->m_insert_type_replace) row_string += "REPLACE INTO ";
79   /*
80    for mysql.innodb_table_stats, mysql.innodb_index_stats tables always
81    dump as INSERT IGNORE INTO
82   */
83   else if (m_options->m_insert_type_ignore ||
84            innodb_stats_tables(row_group->m_source_table->get_schema(),
85                                row_group->m_source_table->get_name()))
86     row_string += "INSERT IGNORE INTO ";
87   else
88     row_string += "INSERT INTO ";
89   row_string += this->get_quoted_object_full_name(row_group->m_source_table);
90   if (m_options->m_dump_column_names || row_group->m_has_generated_columns) {
91     row_string += " (";
92     const std::vector<Mysql_field> &fields = row_group->m_fields;
93     for (std::vector<Mysql_field>::const_iterator field_iterator =
94              fields.begin();
95          field_iterator != fields.end(); ++field_iterator) {
96       if (field_iterator != fields.begin()) row_string += ',';
97       row_string += this->quote_name(field_iterator->get_name());
98     }
99     row_string += ')';
100   }
101   row_string += " VALUES ";
102 
103   CHARSET_INFO *charset_info = this->get_charset();
104 
105   std::vector<bool> is_blob;
106   for (std::vector<Mysql_field>::const_iterator it =
107            row_group->m_fields.begin();
108        it != row_group->m_fields.end(); ++it) {
109     is_blob.push_back(it->get_character_set_nr() == my_charset_bin.number &&
110                       (it->get_type() == MYSQL_TYPE_BIT ||
111                        it->get_type() == MYSQL_TYPE_STRING ||
112                        it->get_type() == MYSQL_TYPE_VAR_STRING ||
113                        it->get_type() == MYSQL_TYPE_VARCHAR ||
114                        it->get_type() == MYSQL_TYPE_BLOB ||
115                        it->get_type() == MYSQL_TYPE_LONG_BLOB ||
116                        it->get_type() == MYSQL_TYPE_MEDIUM_BLOB ||
117                        it->get_type() == MYSQL_TYPE_TINY_BLOB ||
118                        it->get_type() == MYSQL_TYPE_GEOMETRY));
119   }
120 
121   for (std::vector<Row *>::const_iterator row_iterator =
122            row_group->m_rows.begin();
123        row_iterator != row_group->m_rows.end(); ++row_iterator) {
124     Row *row = *row_iterator;
125 
126     if (row_iterator != row_group->m_rows.begin()) row_string += ',';
127     row_string += '(';
128 
129     size_t columns = row->m_row_data.size();
130     for (size_t column = 0; column < columns; ++column) {
131       if (column > 0) row_string += ',';
132 
133       size_t column_length;
134       const char *column_data =
135           row->m_row_data.get_buffer(column, column_length);
136 
137       if (row->m_row_data.is_value_null(column))
138         row_string += "NULL";
139       else if (column_length == 0)
140         row_string += "''";
141       else if (row_group->m_fields[column].get_additional_flags() & NUM_FLAG) {
142         if (column_length >= 1 &&
143             (my_isalpha(charset_info, column_data[0]) ||
144              (column_length >= 2 && column_data[0] == '-' &&
145               my_isalpha(charset_info, column_data[1])))) {
146           row_string += "NULL";
147         } else if (row_group->m_fields[column].get_type() ==
148                    MYSQL_TYPE_DECIMAL) {
149           row_string += '\'';
150           row_string.append(column_data, column_length);
151           row_string += '\'';
152         } else
153           row_string.append(column_data, column_length);
154       } else if (m_options->m_hex_blob && is_blob[column]) {
155         row_string += "0x";
156         Mysql::Tools::Base::Mysql_query_runner::append_hex_string(
157             &row_string, column_data, column_length);
158       } else {
159         if (is_blob[column]) row_string += "_binary ";
160         row_string += '\"';
161         if (m_escaping_runner)
162           m_escaping_runner->append_escape_string(&row_string, column_data,
163                                                   column_length);
164         else
165           row_string.append(column_data, column_length);
166         row_string += '\"';
167       }
168     }
169 
170     row_string += ')';
171   }
172 
173   row_string += ";\n";
174 
175   this->append_output(row_string);
176   /*
177     user account is dumped in the form of INSERT statements, thus need
178     to append FLUSH PRIVILEGES
179   */
180   if (!use_show_create_user) {
181     std::string schema = row_group->m_source_table->get_schema();
182     std::string name = row_group->m_source_table->get_name();
183     if ((schema == "mysql") && (name == "user")) {
184       this->append_output("/*! FLUSH PRIVILEGES */;\n");
185     }
186   }
187 }
188 
format_table_indexes(Table_deferred_indexes_dump_task * table_indexes_dump_task)189 void Sql_formatter::format_table_indexes(
190     Table_deferred_indexes_dump_task *table_indexes_dump_task) {
191   Table *table = table_indexes_dump_task->get_related_table();
192   if (m_options->m_deffer_table_indexes) {
193     /*
194       Tables can have indexes  which can refer to columns from
195       other tables (ex: foreign keys). In that case we need to
196       emit 'USE db' statement as the referenced table may not have
197       been created
198     */
199     bool use_added = false;
200     std::string alter_base_string =
201         "ALTER TABLE " + this->get_quoted_object_full_name(table) + " ADD ";
202     for (std::vector<std::string>::const_iterator it =
203              table->get_indexes_sql_definition().begin();
204          it != table->get_indexes_sql_definition().end(); ++it) {
205       if (!use_added) {
206         this->append_output("USE " + this->quote_name(table->get_schema()) +
207                             ";\n");
208         use_added = true;
209       }
210       this->append_output(alter_base_string + (*it) + ";\n");
211     }
212   }
213   if (m_options->m_add_locks) this->append_output("UNLOCK TABLES;\n");
214 }
215 
format_table_definition(Table_definition_dump_task * table_definition_dump_task)216 void Sql_formatter::format_table_definition(
217     Table_definition_dump_task *table_definition_dump_task) {
218   Table *table = table_definition_dump_task->get_related_table();
219 
220   /*
221    do not dump DDLs for mysql.innodb_table_stats,
222    mysql.innodb_index_stats tables
223   */
224   if (innodb_stats_tables(table->get_schema(), table->get_name())) return;
225   bool use_added = false;
226   if (m_options->m_drop_table)
227     this->append_output("DROP TABLE IF EXISTS " +
228                         this->get_quoted_object_full_name(table) + ";\n");
229   if (m_options->m_deffer_table_indexes == 0 && !use_added) {
230     use_added = true;
231     this->append_output("USE " + this->quote_name(table->get_schema()) + ";\n");
232   }
233   if (!m_options->m_suppress_create_table)
234     this->append_output((m_options->m_deffer_table_indexes
235                              ? table->get_sql_definition_without_indexes()
236                              : table->get_sql_formatted_definition()) +
237                         ";\n");
238 
239   if (m_options->m_add_locks)
240     this->append_output("LOCK TABLES " +
241                         this->get_quoted_object_full_name(table) + " WRITE;\n");
242 }
243 
format_database_start(Database_start_dump_task * database_definition_dump_task)244 void Sql_formatter::format_database_start(
245     Database_start_dump_task *database_definition_dump_task) {
246   Database *database = database_definition_dump_task->get_related_database();
247   if (m_options->m_drop_database)
248     this->append_output("DROP DATABASE IF EXISTS " +
249                         this->quote_name(database->get_name()) + ";\n");
250   if (!m_options->m_suppress_create_database)
251     this->append_output(database->get_sql_formatted_definition() + ";\n");
252 }
253 
format_dump_end(Dump_end_dump_task *)254 void Sql_formatter::format_dump_end(Dump_end_dump_task *) {
255   std::ostringstream out;
256   std::time_t sys_time =
257       std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
258   // Convert to calendar time. time_string ends with '\n'.
259   std::string time_string = std::ctime(&sys_time);
260 
261   if (m_options->m_timezone_consistent)
262     out << "SET TIME_ZONE=@OLD_TIME_ZONE;\n";
263   if (m_options->m_charsets_consistent)
264     out << "SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT;\n"
265            "SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS;\n"
266            "SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION;\n";
267   out << "SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;\n"
268          "SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;\n"
269          "SET SQL_MODE=@OLD_SQL_MODE;\n";
270   if (m_options->m_innodb_stats_tables_included)
271     out << "SET GLOBAL INNODB_STATS_AUTO_RECALC="
272         << "@OLD_INNODB_STATS_AUTO_RECALC;\n";
273   out << "-- Dump end time: " << time_string;
274 
275   this->append_output(out.str());
276 }
277 
format_dump_start(Dump_start_dump_task * dump_start_dump_task)278 void Sql_formatter::format_dump_start(
279     Dump_start_dump_task *dump_start_dump_task) {
280   // Convert to system time.
281   std::time_t sys_time =
282       std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
283   // Convert to calendar time. time_string ends with '\n'.
284   std::string time_string = std::ctime(&sys_time);
285 
286   std::ostringstream out;
287   out << "-- Dump created by MySQL pump utility, version: " MYSQL_SERVER_VERSION
288          ", " SYSTEM_TYPE " (" MACHINE_TYPE ")\n"
289       << "-- Dump start time: " << time_string
290       << "-- Server version: " << this->get_server_version_string() << "\n\n"
291       << "SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;\n"
292          "SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, "
293          "FOREIGN_KEY_CHECKS=0;\n"
294       << "SET @OLD_SQL_MODE=@@SQL_MODE;\n"
295          "SET SQL_MODE=\"NO_AUTO_VALUE_ON_ZERO\";\n";
296 
297   /* disable binlog */
298   out << "SET @@SESSION.SQL_LOG_BIN= 0;\n";
299 
300   if (m_options->m_timezone_consistent)
301     out << "SET @OLD_TIME_ZONE=@@TIME_ZONE;\n"
302            "SET TIME_ZONE='+00:00';\n";
303   if (m_options->m_charsets_consistent)
304     out << "SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT;\n"
305            "SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;\n"
306            "SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION;\n"
307            "SET NAMES "
308         << this->get_charset()->csname << ";\n";
309 
310   if (m_options->m_innodb_stats_tables_included)
311     out << "SET @OLD_INNODB_STATS_AUTO_RECALC="
312         << "@@INNODB_STATS_AUTO_RECALC;\n"
313         << "SET GLOBAL INNODB_STATS_AUTO_RECALC=OFF;\n";
314 
315   if (dump_start_dump_task->m_gtid_mode == "OFF" &&
316       m_options->m_gtid_purged == enum_gtid_purged_mode::GTID_PURGED_ON) {
317     m_options->m_mysql_chain_element_options->get_program()->error(
318         Mysql::Tools::Base::Message_data(
319             1, "Server has GTIDs disabled.\n",
320             Mysql::Tools::Base::Message_type_error));
321     return;
322   }
323   if (dump_start_dump_task->m_gtid_mode != "OFF") {
324     if (m_options->m_gtid_purged == enum_gtid_purged_mode::GTID_PURGED_ON ||
325         m_options->m_gtid_purged == enum_gtid_purged_mode::GTID_PURGED_AUTO) {
326       if (!m_mysqldump_tool_options->m_dump_all_databases &&
327           m_options->m_gtid_purged == enum_gtid_purged_mode::GTID_PURGED_AUTO) {
328         m_options->m_mysql_chain_element_options->get_program()->error(
329             Mysql::Tools::Base::Message_data(
330                 1,
331                 "A partial dump from a server that is using GTID-based "
332                 "replication "
333                 "requires the --set-gtid-purged=[ON|OFF] option to be "
334                 "specified. Use ON "
335                 "if the intention is to deploy a new replication slave using "
336                 "only some "
337                 "of the data from the dumped server. Use OFF if the intention "
338                 "is to "
339                 "repair a table by copying it within a topology, and use OFF "
340                 "if the "
341                 "intention is to copy a table between replication topologies "
342                 "that are "
343                 "disjoint and will remain so.\n",
344                 Mysql::Tools::Base::Message_type_error));
345         return;
346       }
347       std::string gtid_output("SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '");
348       gtid_output += (dump_start_dump_task->m_gtid_executed + "';\n");
349       out << gtid_output;
350     }
351   }
352 
353   this->append_output(out.str());
354 }
355 
format_plain_sql_object(Abstract_plain_sql_object_dump_task * plain_sql_dump_task)356 void Sql_formatter::format_plain_sql_object(
357     Abstract_plain_sql_object_dump_task *plain_sql_dump_task) {
358   View *new_view_task = dynamic_cast<View *>(plain_sql_dump_task);
359   if (new_view_task != nullptr) {
360     /*
361      DROP VIEW statement followed by CREATE VIEW must be written to output
362      as an atomic operation, else there is a possibility of bug#21399236.
363      It happens when we DROP VIEW v1, and it uses column from view v2, which
364      might get dropped before creation of real v1 view, and thus result in
365      error during restore.
366    */
367     format_sql_objects_definer(plain_sql_dump_task, "VIEW");
368     this->append_output(
369         "DROP VIEW IF EXISTS " +
370         this->get_quoted_object_full_name(new_view_task) + ";\n" +
371         plain_sql_dump_task->get_sql_formatted_definition() + ";\n");
372     return;
373   }
374 
375   Mysql_function *new_func_task =
376       dynamic_cast<Mysql_function *>(plain_sql_dump_task);
377   if (new_func_task != nullptr)
378     format_sql_objects_definer(plain_sql_dump_task, "FUNCTION");
379 
380   Stored_procedure *new_proc_task =
381       dynamic_cast<Stored_procedure *>(plain_sql_dump_task);
382   if (new_proc_task != nullptr)
383     format_sql_objects_definer(plain_sql_dump_task, "PROCEDURE");
384 
385   Privilege *new_priv_task = dynamic_cast<Privilege *>(plain_sql_dump_task);
386   if (new_priv_task != nullptr) {
387     if (m_options->m_drop_user)
388       this->append_output(
389           "DROP USER " +
390           (dynamic_cast<Abstract_data_object *>(new_priv_task))->get_name() +
391           ";\n");
392   }
393 
394   Column_statistic *new_col_stats_task =
395       dynamic_cast<Column_statistic *>(plain_sql_dump_task);
396   if (new_col_stats_task != nullptr) {
397     if (m_options->m_column_statistics)
398       this->append_output(plain_sql_dump_task->get_sql_formatted_definition() +
399                           ";\n");
400     return;
401   }
402 
403   this->append_output(plain_sql_dump_task->get_sql_formatted_definition() +
404                       ";\n");
405 }
406 
format_sql_objects_definer(Abstract_plain_sql_object_dump_task * plain_sql_dump_task,std::string object_type)407 void Sql_formatter::format_sql_objects_definer(
408     Abstract_plain_sql_object_dump_task *plain_sql_dump_task,
409     std::string object_type) {
410   if (m_options->m_skip_definer) {
411     std::istringstream ddl_stream(
412         plain_sql_dump_task->get_sql_formatted_definition());
413     std::string new_sql_stmt;
414     bool is_replaced = false;
415     for (std::string object_sql; std::getline(ddl_stream, object_sql);) {
416       size_t object_pos = object_sql.find(object_type);
417       size_t definer_pos = object_sql.find("DEFINER");
418       if (object_pos != std::string::npos && definer_pos != std::string::npos &&
419           definer_pos <= object_pos && !is_replaced) {
420         object_sql.replace(definer_pos, (object_pos - definer_pos), "");
421         new_sql_stmt += object_sql + "\n";
422         is_replaced = true;
423       } else
424         new_sql_stmt += object_sql + "\n";
425     }
426     plain_sql_dump_task->set_sql_formatted_definition(new_sql_stmt);
427   }
428 }
429 
430 /**
431   Check if the table is innodb stats table in mysql database.
432 
433   @param [in] db           Database name
434   @param [in] table        Table name
435 
436   @retval true if it is innodb stats table else false
437 */
innodb_stats_tables(std::string db,std::string table)438 bool Sql_formatter::innodb_stats_tables(std::string db, std::string table) {
439   return ((db == "mysql") &&
440           ((table == "innodb_table_stats") || (table == "innodb_index_stats")));
441 }
442 
format_object(Item_processing_data * item_to_process)443 void Sql_formatter::format_object(Item_processing_data *item_to_process) {
444   this->object_processing_starts(item_to_process);
445 
446   // format_row_group is placed first, as it is most occurring task.
447   if (this->try_process_task<Row_group_dump_task>(
448           item_to_process, &Sql_formatter::format_row_group) ||
449       this->try_process_task<Table_definition_dump_task>(
450           item_to_process, &Sql_formatter::format_table_definition) ||
451       this->try_process_task<Table_deferred_indexes_dump_task>(
452           item_to_process, &Sql_formatter::format_table_indexes) ||
453       this->try_process_task<Dump_start_dump_task>(
454           item_to_process, &Sql_formatter::format_dump_start) ||
455       this->try_process_task<Dump_end_dump_task>(
456           item_to_process, &Sql_formatter::format_dump_end) ||
457       this->try_process_task<Database_start_dump_task>(
458           item_to_process, &Sql_formatter::format_database_start)
459       /*
460         Abstract_plain_sql_object_dump_task must be last, as so of above derive
461         from it too.
462         */
463       || this->try_process_task<Abstract_plain_sql_object_dump_task>(
464              item_to_process, &Sql_formatter::format_plain_sql_object)) {
465     // Item was processed. No further action required.
466   }
467 
468   this->object_processing_ends(item_to_process);
469 
470   return;
471 }
472 
Sql_formatter(I_connection_provider * connection_provider,std::function<bool (const Mysql::Tools::Base::Message_data &)> * message_handler,Simple_id_generator * object_id_generator,const Mysqldump_tool_chain_maker_options * mysqldump_tool_options,const Sql_formatter_options * options)473 Sql_formatter::Sql_formatter(
474     I_connection_provider *connection_provider,
475     std::function<bool(const Mysql::Tools::Base::Message_data &)>
476         *message_handler,
477     Simple_id_generator *object_id_generator,
478     const Mysqldump_tool_chain_maker_options *mysqldump_tool_options,
479     const Sql_formatter_options *options)
480     : Abstract_output_writer_wrapper(message_handler, object_id_generator),
481       Abstract_mysql_chain_element_extension(
482           connection_provider, message_handler,
483           options->m_mysql_chain_element_options),
484       m_mysqldump_tool_options(mysqldump_tool_options),
485       m_options(options) {
486   m_escaping_runner = this->get_runner();
487 }
488 
~Sql_formatter()489 Sql_formatter::~Sql_formatter() {
490   if (m_escaping_runner) delete m_escaping_runner;
491 }
492