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