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 "mysql_crawler.h"
26 #include "mysql_function.h"
27 #include "stored_procedure.h"
28 #include "table_definition_dump_task.h"
29 #include "mysqldump_tool_chain_maker_options.h"
30 #include "table_rows_dump_task.h"
31 #include "table_deferred_indexes_dump_task.h"
32 #include "event_scheduler_event.h"
33 #include "privilege.h"
34 #include "trigger.h"
35 #include "view.h"
36 #include "base/mysql_query_runner.h"
37 #include <string>
38 #include <vector>
39 using std::string;
40 using std::vector;
41 
42 using namespace Mysql::Tools::Dump;
43 
Mysql_crawler(I_connection_provider * connection_provider,Mysql::I_callable<bool,const Mysql::Tools::Base::Message_data &> * message_handler,Simple_id_generator * object_id_generator,Mysql_chain_element_options * options,Mysqldump_tool_chain_maker_options * mysqldump_tool_cmaker_options,Mysql::Tools::Base::Abstract_program * program)44 Mysql_crawler::Mysql_crawler(I_connection_provider* connection_provider,
45   Mysql::I_callable<bool, const Mysql::Tools::Base::Message_data&>*
46     message_handler, Simple_id_generator* object_id_generator,
47   Mysql_chain_element_options* options,
48   Mysqldump_tool_chain_maker_options* mysqldump_tool_cmaker_options,
49   Mysql::Tools::Base::Abstract_program* program)
50   : Abstract_crawler(message_handler, object_id_generator, program),
51   Abstract_mysql_chain_element_extension(
52   connection_provider, message_handler, options),
53   m_mysqldump_tool_cmaker_options(mysqldump_tool_cmaker_options)
54 {}
55 
enumerate_objects()56 void Mysql_crawler::enumerate_objects()
57 {
58   Mysql::Tools::Base::Mysql_query_runner* runner= this->get_runner();
59   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> gtid_mode;
60   std::string gtid_value("OFF");
61   /* Check if the server is GTID enabled */
62   runner->run_query_store("SELECT @@global.gtid_mode", &gtid_mode);
63   if (gtid_mode.size())
64   {
65     std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>
66       ::iterator mode_it= gtid_mode.begin();
67     const Mysql::Tools::Base::Mysql_query_runner::Row& gtid_data= **mode_it;
68     gtid_value= gtid_data[0];
69   }
70   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&gtid_mode);
71 
72   /* get the GTID_EXECUTED value */
73   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> gtid_executed;
74   runner->run_query_store("SELECT @@GLOBAL.GTID_EXECUTED", &gtid_executed);
75 
76   std::string gtid_output_val;
77   if (gtid_executed.size())
78   {
79     std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>
80       ::iterator gtid_executed_iter= gtid_executed.begin();
81     const Mysql::Tools::Base::Mysql_query_runner::Row& gtid_executed_val=
82       **gtid_executed_iter;
83     gtid_output_val= gtid_executed_val[0];
84   }
85   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&gtid_executed);
86 
87   m_dump_start_task= new Dump_start_dump_task(gtid_value, gtid_output_val);
88   m_dump_end_task= new Dump_end_dump_task();
89   m_tables_definition_ready_dump_task=
90     new Tables_definition_ready_dump_task();
91 
92   m_dump_end_task->add_dependency(m_dump_start_task);
93   this->process_dump_task(m_dump_start_task);
94 
95   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> databases;
96   runner->run_query_store("SHOW DATABASES", &databases);
97 
98   std::vector<Database* > db_list;
99   std::vector<Database_end_dump_task* > db_end_task_list;
100   for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>::iterator
101     it= databases.begin(); it != databases.end(); ++it)
102   {
103     std::string db_name= (**it)[0];
104 
105     Database* database= new Database(
106       this->generate_new_object_id(), db_name,
107       this->get_create_statement(runner, "", db_name,
108       "DATABASE IF NOT EXISTS").value());
109 
110     m_current_database_start_dump_task=
111       new Database_start_dump_task(database);
112 
113     Abstract_data_object* db_object = dynamic_cast<Abstract_data_object*>(
114     m_current_database_start_dump_task->get_related_db_object());
115 
116     /*
117      This check is introduced so that only the database objects that are
118      included in the dump are validated.
119     */
120     if (!m_mysqldump_tool_cmaker_options->is_object_included_in_dump(db_object))
121     {
122       delete m_current_database_start_dump_task;
123       delete database;
124       continue;
125     }
126     db_list.push_back(database);
127     m_current_database_end_dump_task=
128       new Database_end_dump_task(database);
129     db_end_task_list.push_back(m_current_database_end_dump_task);
130 
131     m_current_database_start_dump_task->add_dependency(m_dump_start_task);
132     m_dump_end_task->add_dependency(m_current_database_start_dump_task);
133     m_dump_end_task->add_dependency(m_current_database_end_dump_task);
134 
135     this->process_dump_task(m_current_database_start_dump_task);
136     this->enumerate_database_objects(*database);
137     m_current_database_start_dump_task= NULL;
138   }
139 
140   m_dump_end_task->add_dependency(m_tables_definition_ready_dump_task);
141   this->process_dump_task(m_tables_definition_ready_dump_task);
142 
143   /* SHOW CREATE USER is introduced in 5.7.6 */
144   if (use_show_create_user)
145     this->enumerate_users();
146 
147   std::vector<Database* >::iterator it;
148   std::vector<Database_end_dump_task* >::iterator it_end;
149   for (it= db_list.begin(),it_end= db_end_task_list.begin();
150        ((it != db_list.end()) && (it_end != db_end_task_list.end()));
151         ++it, ++it_end)
152   {
153     m_current_database_end_dump_task= *it_end;
154     this->enumerate_views(**it);
155     this->process_dump_task(m_current_database_end_dump_task);
156     m_current_database_end_dump_task= NULL;
157   }
158 
159   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&databases);
160 
161   this->process_dump_task(m_dump_end_task);
162 
163   this->report_crawler_completed(this);
164 
165   this->wait_for_tasks_completion();
166   delete runner;
167 }
168 
enumerate_database_objects(const Database & db)169 void Mysql_crawler::enumerate_database_objects(const Database& db)
170 {
171   this->enumerate_tables(db);
172   this->enumerate_functions<Mysql_function>(db, "FUNCTION");
173   this->enumerate_functions<Stored_procedure>(db, "PROCEDURE");
174   this->enumerate_event_scheduler_events(db);
175 }
176 
enumerate_tables(const Database & db)177 void Mysql_crawler::enumerate_tables(const Database& db)
178 {
179   Mysql::Tools::Base::Mysql_query_runner* runner= this->get_runner();
180   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> tables;
181 
182   runner->run_query_store("SHOW TABLE STATUS FROM "
183     + this->quote_name(db.get_name()), &tables);
184 
185   for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>::iterator
186     it= tables.begin(); it != tables.end(); ++it)
187   {
188     const Mysql::Tools::Base::Mysql_query_runner::Row& table_data= **it;
189 
190     std::string table_name= table_data[0]; // "Name"
191     std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> fields_data;
192     runner->run_query_store("SHOW COLUMNS IN " + this->quote_name(table_name)
193       + " FROM " + this->quote_name(db.get_name()), &fields_data);
194     std::vector<Field> fields;
195     for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>
196       ::iterator field_it=
197         fields_data.begin(); field_it != fields_data.end(); ++field_it)
198     {
199       fields.push_back(Field((**field_it)[0], (**field_it)[1]));
200     }
201     Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&fields_data);
202     /*
203       For views create a dummy view so that dependent objects are
204       resolved when actually dumping views.
205     */
206 
207     if (table_data.is_value_null(1))
208     {
209       std::string fake_view_ddl = "CREATE VIEW "
210           + this->get_quoted_object_full_name(db.get_name(), table_name)
211           + " AS SELECT\n";
212 
213       for (std::vector<Field>::iterator field_iterator= fields.begin();
214            field_iterator != fields.end();
215            ++field_iterator)
216       {
217         fake_view_ddl+= " 1 AS " + this->quote_name(field_iterator->get_name());
218         if (field_iterator + 1 != fields.end())
219           fake_view_ddl += ",\n";
220       }
221 
222       View* fake_view= new View(this->generate_new_object_id(),
223                            table_name,
224                            db.get_name(),
225                            fake_view_ddl);
226 
227       fake_view->add_dependency(m_current_database_start_dump_task);
228       m_current_database_end_dump_task->add_dependency(fake_view);
229       m_tables_definition_ready_dump_task->add_dependency(fake_view);
230       this->process_dump_task(fake_view);
231       continue;
232     }
233 
234     uint64 rows= table_data.is_value_null(4)
235       ? 0 : atoll(table_data[4].c_str()); // "Rows"
236     bool isInnoDB= table_data[1] == "InnoDB"; // "Engine"
237     Table* table= new Table(this->generate_new_object_id(),
238       table_name,
239       db.get_name(),
240       this->get_create_statement(
241       runner, db.get_name(), table_name, "TABLE")
242       .value(),
243       fields, table_data[1], rows,
244       (uint64)(rows * (isInnoDB ? 1.5 : 1)),
245       atoll(table_data[6].c_str()) // "Data_length"
246       );
247 
248     Table_definition_dump_task* ddl_task=
249       new Table_definition_dump_task(table);
250     Table_rows_dump_task* rows_task= new Table_rows_dump_task(table);
251     Table_deferred_indexes_dump_task* indexes_task=
252       new Table_deferred_indexes_dump_task(table);
253 
254     ddl_task->add_dependency(m_current_database_start_dump_task);
255     rows_task->add_dependency(ddl_task);
256     indexes_task->add_dependency(rows_task);
257     m_current_database_end_dump_task->add_dependency(indexes_task);
258     m_tables_definition_ready_dump_task->add_dependency(ddl_task);
259 
260     this->process_dump_task(ddl_task);
261     this->process_dump_task(rows_task);
262 
263     this->enumerate_table_triggers(*table, rows_task);
264 
265     this->process_dump_task(indexes_task);
266   }
267   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&tables);
268   delete runner;
269 }
270 
enumerate_views(const Database & db)271 void Mysql_crawler::enumerate_views(const Database& db)
272 {
273   Mysql::Tools::Base::Mysql_query_runner* runner= this->get_runner();
274   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> tables;
275 
276   runner->run_query_store("SHOW TABLES FROM "
277     + this->quote_name(db.get_name()), &tables);
278 
279   for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>::iterator
280     it= tables.begin(); it != tables.end(); ++it)
281   {
282     const Mysql::Tools::Base::Mysql_query_runner::Row& table_data= **it;
283 
284     std::string table_name= table_data[0]; // "View Name"
285     std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> check_view;
286     runner->run_query_store("SELECT COUNT(*) FROM "
287             + this->get_quoted_object_full_name("INFORMATION_SCHEMA", "VIEWS")
288             + " WHERE TABLE_NAME= '" + runner->escape_string(table_name)
289             + "' AND TABLE_SCHEMA= '" + runner->escape_string(db.get_name()) + "'",
290             &check_view);
291 
292     for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>::iterator
293          view_it= check_view.begin(); view_it != check_view.end(); ++view_it)
294     {
295       const Mysql::Tools::Base::Mysql_query_runner::Row& is_view= **view_it;
296       if (is_view[0] == "1")
297       {
298         /* Check if view dependent objects exists */
299         if (runner->run_query(std::string("LOCK TABLES ")
300               + this->get_quoted_object_full_name(db.get_name(), table_name)
301               + " READ") != 0)
302         {
303           Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&check_view);
304           Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&tables);
305           delete runner;
306           return;
307         }
308         else
309           runner->run_query(std::string("UNLOCK TABLES"));
310 
311         View* view= new View(this->generate_new_object_id(),
312                               table_name,
313                               db.get_name(),
314                               this->get_create_statement(runner,
315                                                          db.get_name(),
316                                                          table_name,
317                                                          "TABLE").value()
318                               );
319         m_current_database_end_dump_task->add_dependency(view);
320         view->add_dependency(m_tables_definition_ready_dump_task);
321         this->process_dump_task(view);
322       }
323     }
324     Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&check_view);
325   }
326   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&tables);
327   delete runner;
328 }
329 
330 template<typename TObject>
enumerate_functions(const Database & db,std::string type)331 void Mysql_crawler::enumerate_functions(const Database& db, std::string type)
332 {
333   Mysql::Tools::Base::Mysql_query_runner* runner= this->get_runner();
334 
335   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> functions;
336   runner->run_query_store("SHOW " + type + " STATUS WHERE db = '"
337     + runner->escape_string(db.get_name()) + '\'', &functions);
338 
339   for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>::iterator
340     it= functions.begin(); it != functions.end(); ++it)
341   {
342     const Mysql::Tools::Base::Mysql_query_runner::Row& function_row= **it;
343 
344     TObject* function= new TObject(this->generate_new_object_id(),
345       function_row[1], db.get_name(),
346       "DELIMITER //\n" + this->get_create_statement(
347       runner, db.get_name(), function_row[1], type, 2).value()
348       + "//\n" + "DELIMITER ;\n");
349 
350     function->add_dependency(m_current_database_start_dump_task);
351     m_current_database_end_dump_task->add_dependency(function);
352 
353     this->process_dump_task(function);
354   }
355   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&functions);
356   delete runner;
357 }
358 
enumerate_event_scheduler_events(const Database & db)359 void Mysql_crawler::enumerate_event_scheduler_events(const Database& db)
360 {
361   Mysql::Tools::Base::Mysql_query_runner* runner= this->get_runner();
362 
363   // Workaround for "access denied" error fixed in 5.7.6.
364   if (this->get_server_version() < 50706
365     && this->compare_no_case_latin_with_db_string(
366     "performance_schema", db.get_name()) == 0)
367   {
368     return;
369   }
370 
371   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> events;
372   runner->run_query_store("SHOW EVENTS FROM "
373     + this->get_quoted_object_full_name(&db), &events);
374 
375   for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>::iterator
376     it= events.begin(); it != events.end(); ++it)
377   {
378     const Mysql::Tools::Base::Mysql_query_runner::Row& event_row= **it;
379 
380     Event_scheduler_event* event=
381       new Event_scheduler_event(this->generate_new_object_id(),
382       event_row[1], db.get_name(),
383       "DELIMITER //\n" + this->get_create_statement(
384       runner, db.get_name(), event_row[1], "EVENT", 3).value()
385       + "//\n" + "DELIMITER ;\n");
386 
387     event->add_dependency(m_current_database_start_dump_task);
388     m_current_database_end_dump_task->add_dependency(event);
389 
390     this->process_dump_task(event);
391   }
392   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&events);
393   delete runner;
394 }
395 
enumerate_users()396 void Mysql_crawler::enumerate_users()
397 {
398   Mysql::Tools::Base::Mysql_query_runner* runner= this->get_runner();
399 
400   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> users;
401   runner->run_query_store(
402     "SELECT CONCAT(QUOTE(user),'@',QUOTE(host)) FROM mysql.user", &users);
403 
404   for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>::iterator
405     it= users.begin(); it != users.end(); ++it)
406   {
407     const Mysql::Tools::Base::Mysql_query_runner::Row& user_row= **it;
408 
409     std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> create_user;
410     std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> user_grants;
411     if (runner->run_query_store(
412       "SHOW CREATE USER " + user_row[0], &create_user))
413       return;
414     if (runner->run_query_store(
415       "SHOW GRANTS FOR " + user_row[0], &user_grants))
416       return;
417 
418     Abstract_dump_task* previous_grant= m_dump_start_task;
419 
420     std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> ::iterator
421       it1= create_user.begin();
422     const Mysql::Tools::Base::Mysql_query_runner::Row& create_row= **it1;
423 
424     std::string user= create_row[0];
425     for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>
426        ::iterator it2= user_grants.begin(); it2 != user_grants.end(); ++it2)
427     {
428       const Mysql::Tools::Base::Mysql_query_runner::Row& grant_row= **it2;
429       user+= std::string(";\n" + grant_row[0]);
430     }
431     Privilege* grant=
432       new Privilege(
433         this->generate_new_object_id(), user_row[0], user);
434 
435     grant->add_dependency(previous_grant);
436     grant->add_dependency(m_tables_definition_ready_dump_task);
437     m_dump_end_task->add_dependency(grant);
438     this->process_dump_task(grant);
439     previous_grant= grant;
440 
441     Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&create_user);
442     Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&user_grants);
443   }
444   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&users);
445   delete runner;
446 }
447 
enumerate_table_triggers(const Table & table,Abstract_dump_task * dependency)448 void Mysql_crawler::enumerate_table_triggers(
449   const Table& table, Abstract_dump_task* dependency)
450 {
451   // Triggers were supported since 5.0.9
452   if (this->get_server_version() < 50009)
453     return;
454 
455   Mysql::Tools::Base::Mysql_query_runner* runner= this->get_runner();
456 
457   std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*> triggers;
458   runner->run_query_store("SHOW TRIGGERS FROM "
459     + this->quote_name(table.get_schema()) + " LIKE '"
460     + runner->escape_string(table.get_name()) + '\'', &triggers);
461 
462   for (std::vector<const Mysql::Tools::Base::Mysql_query_runner::Row*>::iterator
463     it= triggers.begin(); it != triggers.end(); ++it)
464   {
465     const Mysql::Tools::Base::Mysql_query_runner::Row& trigger_row= **it;
466     Trigger* trigger= new Trigger(this->generate_new_object_id(),
467       trigger_row[0], table.get_schema(),
468       "DELIMITER //\n" + this->get_version_specific_statement(
469       this->get_create_statement(
470       runner, table.get_schema(), trigger_row[0], "TRIGGER", 2).value(),
471       "TRIGGER", "50017", "50003") + "\n//\n" + "DELIMITER ;\n",
472       &table);
473 
474     trigger->add_dependency(dependency);
475     m_current_database_end_dump_task->add_dependency(trigger);
476 
477     this->process_dump_task(trigger);
478   }
479   Mysql::Tools::Base::Mysql_query_runner::cleanup_result(&triggers);
480   delete runner;
481 }
482 
483 
get_version_specific_statement(std::string create_string,const std::string & keyword,std::string main_version,std::string definer_version)484 std::string Mysql_crawler::get_version_specific_statement(
485   std::string create_string, const std::string& keyword,
486   std::string main_version, std::string definer_version)
487 {
488   size_t keyword_pos= create_string.find(" " + keyword);
489   size_t definer_pos= create_string.find(" DEFINER");
490   if (keyword_pos != std::string::npos && definer_pos != std::string::npos
491     && definer_pos < keyword_pos)
492   {
493     create_string.insert(keyword_pos, "*/ /*!" + main_version);
494     create_string.insert(definer_pos, "*/ /*!" + definer_version);
495   }
496   return "/*!" + main_version + ' ' + create_string + " */";
497 }
498