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