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 *((ulong*)&m_options->m_gtid_purged) == ((ulong)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 /*
334 value for m_gtid_purged is set by typecasting its address to ulong*
335 however below conditions fails if we do direct comparison without
336 typecasting on solaris sparc. Guessing that this is due to differnt
337 endianess.
338 */
339 if (*((ulong*)&m_options->m_gtid_purged) == ((ulong)GTID_PURGED_ON) ||
340 *((ulong*)&m_options->m_gtid_purged) == ((ulong)GTID_PURGED_AUTO))
341 {
342 if (!m_mysqldump_tool_options->m_dump_all_databases &&
343 *((ulong*)&m_options->m_gtid_purged) == ((ulong)GTID_PURGED_AUTO))
344 {
345 m_options->m_mysql_chain_element_options->get_program()->error(
346 Mysql::Tools::Base::Message_data(1,
347 "A partial dump from a server that is using GTID-based replication "
348 "requires the --set-gtid-purged=[ON|OFF] option to be specified. Use ON "
349 "if the intention is to deploy a new replication slave using only some "
350 "of the data from the dumped server. Use OFF if the intention is to "
351 "repair a table by copying it within a topology, and use OFF if the "
352 "intention is to copy a table between replication topologies that are "
353 "disjoint and will remain so.\n",
354 Mysql::Tools::Base::Message_type_error));
355 return;
356 }
357 std::string gtid_output("SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '");
358 gtid_output+= (dump_start_dump_task->m_gtid_executed + "';\n");
359 out << gtid_output;
360 }
361 }
362
363 this->append_output(out.str());
364 }
365
format_plain_sql_object(Abstract_plain_sql_object_dump_task * plain_sql_dump_task)366 void Sql_formatter::format_plain_sql_object(
367 Abstract_plain_sql_object_dump_task* plain_sql_dump_task)
368 {
369 View* new_view_task=
370 dynamic_cast<View*>(plain_sql_dump_task);
371 if (new_view_task != NULL)
372 {
373 /*
374 DROP VIEW statement followed by CREATE VIEW must be written to output
375 as an atomic operation, else there is a possibility of bug#21399236.
376 It happens when we DROP VIEW v1, and it uses column from view v2, which
377 might get dropped before creation of real v1 view, and thus result in
378 error during restore.
379 */
380 format_sql_objects_definer(plain_sql_dump_task, "VIEW");
381 this->append_output("DROP VIEW IF EXISTS "
382 + this->get_quoted_object_full_name(new_view_task) + ";\n"
383 + plain_sql_dump_task->get_sql_formatted_definition() + ";\n");
384 return;
385 }
386
387 Mysql_function* new_func_task=
388 dynamic_cast<Mysql_function*>(plain_sql_dump_task);
389 if (new_func_task != NULL)
390 format_sql_objects_definer(plain_sql_dump_task, "FUNCTION");
391
392 Stored_procedure* new_proc_task=
393 dynamic_cast<Stored_procedure*>(plain_sql_dump_task);
394 if (new_proc_task != NULL)
395 format_sql_objects_definer(plain_sql_dump_task, "PROCEDURE");
396
397 Privilege* new_priv_task=
398 dynamic_cast<Privilege*>(plain_sql_dump_task);
399 if (new_priv_task != NULL)
400 {
401 if (m_options->m_drop_user)
402 this->append_output("DROP USER "
403 + (dynamic_cast<Abstract_data_object*>(new_priv_task))->get_name()
404 + ";\n");
405 }
406
407 this->append_output(plain_sql_dump_task->get_sql_formatted_definition()
408 + ";\n");
409 }
410
format_sql_objects_definer(Abstract_plain_sql_object_dump_task * plain_sql_dump_task,std::string object_type)411 void Sql_formatter::format_sql_objects_definer(
412 Abstract_plain_sql_object_dump_task* plain_sql_dump_task, std::string object_type)
413 {
414 if (m_options->m_skip_definer)
415 {
416 std::vector<std::string> object_ddl_lines;
417 std::string object_ddl(plain_sql_dump_task->get_sql_formatted_definition());
418 boost::split(object_ddl_lines, object_ddl,
419 boost::is_any_of("\n"), boost::token_compress_on);
420
421 std::string new_sql_stmt;
422 bool is_replaced= FALSE;
423 for (std::vector<std::string>::iterator it= object_ddl_lines.begin();
424 it != object_ddl_lines.end(); ++it)
425 {
426 std::string object_sql(*it);
427 size_t object_pos= object_sql.find(object_type);
428 size_t definer_pos= object_sql.find("DEFINER");
429 if (object_pos != std::string::npos &&
430 definer_pos != std::string::npos &&
431 definer_pos <= object_pos &&
432 !is_replaced)
433 {
434 object_sql.replace(definer_pos, (object_pos-definer_pos), "");
435 new_sql_stmt+= object_sql + "\n";
436 is_replaced= TRUE;
437 }
438 else
439 new_sql_stmt+= object_sql + "\n";
440 }
441 plain_sql_dump_task->set_sql_formatted_definition(new_sql_stmt);
442 }
443 }
format_object(Item_processing_data * item_to_process)444 void Sql_formatter::format_object(Item_processing_data* item_to_process)
445 {
446 this->object_processing_starts(item_to_process);
447
448 // format_row_group is placed first, as it is most occurring task.
449 if (this->try_process_task<Row_group_dump_task>
450 (item_to_process, &Sql_formatter::format_row_group)
451 || this->try_process_task<Table_definition_dump_task>
452 (item_to_process, &Sql_formatter::format_table_definition)
453 || this->try_process_task<Table_deferred_indexes_dump_task>
454 (item_to_process, &Sql_formatter::format_table_indexes)
455 || this->try_process_task<Dump_start_dump_task>
456 (item_to_process, &Sql_formatter::format_dump_start)
457 || this->try_process_task<Dump_end_dump_task>
458 (item_to_process, &Sql_formatter::format_dump_end)
459 || this->try_process_task<Database_start_dump_task>
460 (item_to_process, &Sql_formatter::format_database_start)
461 /*
462 Abstract_plain_sql_object_dump_task must be last, as so of above derive
463 from it too.
464 */
465 || this->try_process_task<Abstract_plain_sql_object_dump_task>
466 (item_to_process, &Sql_formatter::format_plain_sql_object))
467 {
468 // Item was processed. No further action required.
469 }
470
471 this->object_processing_ends(item_to_process);
472
473 return;
474 }
475
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)476 Sql_formatter::Sql_formatter(I_connection_provider* connection_provider,
477 Mysql::I_callable<bool, const Mysql::Tools::Base::Message_data&>*
478 message_handler, Simple_id_generator* object_id_generator,
479 const Mysqldump_tool_chain_maker_options* mysqldump_tool_options,
480 const Sql_formatter_options* options)
481 : Abstract_output_writer_wrapper(message_handler, object_id_generator),
482 Abstract_mysql_chain_element_extension(
483 connection_provider, message_handler,
484 options->m_mysql_chain_element_options),
485 m_mysqldump_tool_options(mysqldump_tool_options),
486 m_options(options)
487 {
488 m_escaping_runner= this->get_runner();
489 }
490
~Sql_formatter()491 Sql_formatter::~Sql_formatter()
492 {
493 delete m_escaping_runner;
494 }
495