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