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