1 /*
2    Copyright (c) 2001, 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 "client_priv.h"
26 #include "my_default.h"
27 #include "mysqlcheck.h"
28 #include <m_ctype.h>
29 #include <mysql_version.h>
30 #include <mysqld_error.h>
31 
32 #include <string>
33 #include <vector>
34 
35 using namespace Mysql::Tools::Check;
36 
37 using std::string;
38 using std::vector;
39 
40 
41 /* ALTER instead of repair. */
42 #define MAX_ALTER_STR_SIZE 128 * 1024
43 #define KEY_PARTITIONING_CHANGED_STR "KEY () partitioning changed"
44 
45 static MYSQL *sock= 0;
46 static my_bool opt_alldbs= 0, opt_check_only_changed= 0, opt_extended= 0,
47                opt_databases= 0, opt_fast= 0,
48                opt_medium_check = 0, opt_quick= 0, opt_all_in_1= 0,
49                opt_silent= 0, opt_auto_repair= 0, ignore_errors= 0,
50                opt_frm= 0, opt_fix_table_names= 0, opt_fix_db_names= 0, opt_upgrade= 0,
51                opt_write_binlog= 1;
52 static uint verbose = 0;
53 static string opt_skip_database;
54 int what_to_do = 0;
55 
56 void (*DBError)(MYSQL *mysql, string when);
57 
58 static int first_error = 0;
59 vector<string> tables4repair, tables4rebuild, alter_table_cmds, failed_tables;
60 
61 
62 static int process_all_databases();
63 static int process_databases(vector<string> db_names);
64 static int process_selected_tables(string db, vector<string> table_names);
65 static int process_all_tables_in_db(string database);
66 static int process_one_db(string database);
67 static int use_db(string database);
68 static int handle_request_for_tables(string tables);
69 static void print_result();
70 static string escape_table_name(string src);
71 
72 
process_all_databases()73 static int process_all_databases()
74 {
75   MYSQL_ROW row;
76   MYSQL_RES *tableres;
77   int result = 0;
78 
79   if (mysql_query(sock, "SHOW DATABASES") ||
80       !(tableres = mysql_store_result(sock)))
81   {
82     my_printf_error(0, "Error: Couldn't execute 'SHOW DATABASES': %s",
83                     MYF(0), mysql_error(sock));
84     return 1;
85   }
86   while ((row = mysql_fetch_row(tableres)))
87   {
88     if (process_one_db(row[0]))
89       result = 1;
90   }
91   mysql_free_result(tableres);
92   return result;
93 }
94 /* process_all_databases */
95 
96 
process_databases(vector<string> db_names)97 static int process_databases(vector<string> db_names)
98 {
99   int result = 0;
100   vector<string>::iterator it;
101   for (it= db_names.begin() ; it != db_names.end(); it++)
102   {
103     if (process_one_db(*it))
104       result = 1;
105   }
106   return result;
107 } /* process_databases */
108 
109 
process_selected_tables(string db,vector<string> table_names)110 static int process_selected_tables(string db, vector<string> table_names)
111 {
112   if (use_db(db))
113     return 1;
114   vector<string>::iterator it;
115 
116   for (it= table_names.begin(); it != table_names.end(); it++)
117   {
118     if (what_to_do != DO_UPGRADE)
119       *it= escape_table_name(*it);
120     handle_request_for_tables(*it);
121   }
122   return 0;
123 } /* process_selected_tables */
124 
125 
escape_str(string src,size_t start,size_t end,string & res)126 static inline void escape_str(string src, size_t start, size_t end, string &res)
127 {
128   res+= '`';
129   for (size_t i= start; i < end; i++)
130   {
131     switch (src[i]) {
132     case '`':            /* Escape backtick character. */
133       res+= '`';
134       /* Fall through. */
135     default:
136       res+= src[i];
137     }
138   }
139   res+= '`';
140 }
141 
142 
escape_table_name(string src)143 static string escape_table_name(string src)
144 {
145   string res= "";
146 
147   escape_str(src, 0, src.length(), res);
148   return res;
149 }
150 
151 
escape_db_table_name(string src,size_t dot_pos)152 static string escape_db_table_name(string src, size_t dot_pos)
153 {
154   string res= "";
155 
156   /* Escape database name. */
157   escape_str(src, 0, dot_pos - 1, res);
158   /* Add a dot. */
159   res+= '.';
160   /* Escape table name. */
161   escape_str(src, dot_pos, src.length(), res);
162 
163   return res;
164 }
165 
process_all_tables_in_db(string database)166 static int process_all_tables_in_db(string database)
167 {
168   MYSQL_RES *res= NULL;
169   MYSQL_ROW row;
170   uint num_columns;
171 
172   if (use_db(database))
173     return 1;
174   if ((mysql_query(sock, "SHOW /*!50002 FULL*/ TABLES") &&
175        mysql_query(sock, "SHOW TABLES")) ||
176       !(res= mysql_store_result(sock)))
177   {
178     my_printf_error(0, "Error: Couldn't get table list for database %s: %s",
179                     MYF(0), database.c_str(), mysql_error(sock));
180     return 1;
181   }
182 
183   num_columns= mysql_num_fields(res);
184 
185   vector<string> table_names;
186 
187   while ((row = mysql_fetch_row(res)))
188   {
189     /* Skip views if we don't perform renaming. */
190     if ((what_to_do != DO_UPGRADE) && (num_columns == 2) && (strcmp(row[1], "VIEW") == 0))
191       continue;
192 
193     table_names.push_back(row[0]);
194   }
195   mysql_free_result(res);
196 
197   process_selected_tables(database, table_names);
198   return 0;
199 } /* process_all_tables_in_db */
200 
201 
run_query(string query)202 static int run_query(string query)
203 {
204   if (mysql_query(sock, query.c_str()))
205   {
206     fprintf(stderr, "Failed to run query \"%s\"\n", query.c_str());
207     fprintf(stderr, "Error: %s\n", mysql_error(sock));
208     return 1;
209   }
210   return 0;
211 }
212 
213 
fix_table_storage_name(string name)214 static int fix_table_storage_name(string name)
215 {
216   if (strncmp(name.c_str(), "#mysql50#", 9))
217     return 1;
218   int rc= run_query("RENAME TABLE `" + name + "` TO `" + name.substr(9) + "`");
219   if (verbose)
220     printf("%-50s %s\n", name.c_str(), rc ? "FAILED" : "OK");
221   return rc;
222 }
223 
fix_database_storage_name(string name)224 static int fix_database_storage_name(string name)
225 {
226   if (strncmp(name.c_str(), "#mysql50#", 9))
227     return 1;
228   int rc= run_query("ALTER DATABASE `" + name + "` UPGRADE DATA DIRECTORY NAME");
229   if (verbose)
230     printf("%-50s %s\n", name.c_str(), rc ? "FAILED" : "OK");
231   return rc;
232 }
233 
rebuild_table(string name)234 static int rebuild_table(string name)
235 {
236   int rc= 0;
237   string query= "ALTER TABLE " + name + " FORCE";
238   if (mysql_real_query(sock, query.c_str(), (ulong)query.length()))
239   {
240     fprintf(stderr, "Failed to %s\n", query.c_str());
241     fprintf(stderr, "Error: %s\n", mysql_error(sock));
242     rc= 1;
243   }
244   else
245     printf("%s\nRunning  : %s\nstatus   : OK\n", name.c_str(), query.c_str());
246   return rc;
247 }
248 
process_one_db(string database)249 static int process_one_db(string database)
250 {
251   if (opt_skip_database.length() > 0 && opt_alldbs
252     && database == opt_skip_database)
253     return 0;
254 
255   if (what_to_do == DO_UPGRADE)
256   {
257     int rc= 0;
258     if (opt_fix_db_names && !strncmp(database.c_str(),"#mysql50#", 9))
259     {
260       rc= fix_database_storage_name(database);
261       database= database.substr(9);
262     }
263     if (rc || !opt_fix_table_names)
264       return rc;
265   }
266   return process_all_tables_in_db(database);
267 }
268 
269 
use_db(string database)270 static int use_db(string database)
271 {
272   if (mysql_get_server_version(sock) >= FIRST_INFORMATION_SCHEMA_VERSION &&
273       !my_strcasecmp(
274         &my_charset_latin1, database.c_str(), INFORMATION_SCHEMA_DB_NAME))
275     return 1;
276   if (mysql_get_server_version(sock) >= FIRST_PERFORMANCE_SCHEMA_VERSION &&
277       !my_strcasecmp(
278         &my_charset_latin1, database.c_str(), PERFORMANCE_SCHEMA_DB_NAME))
279     return 1;
280   if (mysql_select_db(sock, database.c_str()))
281   {
282     DBError(sock, "when selecting the database");
283     return 1;
284   }
285   return 0;
286 } /* use_db */
287 
disable_binlog()288 static int disable_binlog()
289 {
290   return run_query("SET SQL_LOG_BIN=0");
291 }
292 
handle_request_for_tables(string tables)293 static int handle_request_for_tables(string tables)
294 {
295   string operation, options;
296 
297   switch (what_to_do) {
298   case DO_CHECK:
299     operation = "CHECK";
300     if (opt_quick)              options+= " QUICK";
301     if (opt_fast)               options+= " FAST";
302     if (opt_medium_check)       options+= " MEDIUM"; /* Default */
303     if (opt_extended)           options+= " EXTENDED";
304     if (opt_check_only_changed) options+= " CHANGED";
305     if (opt_upgrade)            options+= " FOR UPGRADE";
306     break;
307   case DO_REPAIR:
308     operation= (opt_write_binlog) ? "REPAIR" : "REPAIR NO_WRITE_TO_BINLOG";
309     if (opt_quick)              options+= " QUICK";
310     if (opt_extended)           options+= " EXTENDED";
311     if (opt_frm)                options+= " USE_FRM";
312     break;
313   case DO_ANALYZE:
314     operation= (opt_write_binlog) ? "ANALYZE" : "ANALYZE NO_WRITE_TO_BINLOG";
315     break;
316   case DO_OPTIMIZE:
317     operation= (opt_write_binlog) ? "OPTIMIZE" : "OPTIMIZE NO_WRITE_TO_BINLOG";
318     break;
319   case DO_UPGRADE:
320     return fix_table_storage_name(tables);
321   }
322 
323   string query= operation + " TABLE " + tables + " " + options;
324 
325   if (mysql_real_query(sock, query.c_str(), (ulong)query.length()))
326   {
327     DBError(sock,
328       "when executing '" + operation + " TABLE ... " + options + "'");
329     return 1;
330   }
331   print_result();
332   return 0;
333 }
334 
335 
print_result()336 static void print_result()
337 {
338   MYSQL_RES *res;
339   MYSQL_ROW row;
340   char prev[NAME_LEN*3+2];
341   char prev_alter[MAX_ALTER_STR_SIZE];
342   uint i;
343   size_t dot_pos;
344   my_bool found_error=0, table_rebuild=0;
345 
346   res = mysql_use_result(sock);
347   dot_pos= strlen(sock->db) + 1;
348 
349   prev[0] = '\0';
350   prev_alter[0]= 0;
351   for (i = 0; (row = mysql_fetch_row(res)); i++)
352   {
353     int changed = strcmp(prev, row[0]);
354     my_bool status = !strcmp(row[2], "status");
355 
356     if (status)
357     {
358       /*
359         if there was an error with the table, we have --auto-repair set,
360         and this isn't a repair op, then add the table to the tables4repair
361         list
362       */
363       if (found_error && opt_auto_repair && what_to_do != DO_REPAIR &&
364           strcmp(row[3],"OK"))
365       {
366         if (table_rebuild)
367         {
368           if (prev_alter[0])
369             alter_table_cmds.push_back(prev_alter);
370           else
371             tables4rebuild.push_back(escape_db_table_name(prev, dot_pos));
372         }
373         else
374         {
375           tables4repair.push_back(escape_db_table_name(prev, dot_pos));
376         }
377 
378       }
379       found_error=   0;
380       table_rebuild= 0;
381       prev_alter[0]= '\0';
382       if (opt_silent)
383         continue;
384     }
385     if (status && changed)
386       printf("%-50s %s", row[0], row[3]);
387     else if (!status && changed)
388     {
389       printf("%s\n%-9s: %s", row[0], row[2], row[3]);
390       if (opt_auto_repair && strcmp(row[2],"note"))
391       {
392         const char *alter_txt= strstr(row[3], "ALTER TABLE");
393         found_error= 1;
394 
395         if (alter_txt)
396         {
397           table_rebuild= 1;
398           const char *match_str;
399           if (!strncmp(row[3], KEY_PARTITIONING_CHANGED_STR,
400                        strlen(KEY_PARTITIONING_CHANGED_STR)))
401           {
402             if (strstr(alter_txt, "PARTITION BY") &&
403                 strlen(alter_txt) < MAX_ALTER_STR_SIZE)
404             {
405               strncpy(prev_alter, alter_txt, MAX_ALTER_STR_SIZE-1);
406               prev_alter[MAX_ALTER_STR_SIZE-1]= 0;
407             }
408             else
409             {
410                printf("\nError: Alter command unknown or too long (%d >= %d), "
411                       "please investigate the above or dump/reload to fix it!"
412                       "\n",
413                       (int)strlen(alter_txt),
414                       MAX_ALTER_STR_SIZE);
415               found_error=   0;
416               table_rebuild= 0;
417               prev_alter[0]= '\0';
418               failed_tables.push_back(row[0]);
419             }
420           }
421           else if ((match_str= strstr(alter_txt, "` UPGRADE PARTITIONING")) &&
422                    strlen(match_str) == 22)
423           {
424             strcpy(prev_alter, alter_txt);
425           }
426         }
427 	else
428         {
429           /*
430             Search the error message specific to pre 5.0 decimal type.
431             "REPAIR TABLE" should not be present in the error message and
432             "dump/reload" should be present in the error message. In this
433             case, do not add table to the repair list.
434           */
435           const char *repair_txt= strstr(row[3], "REPAIR TABLE");
436           const char *dump_txt= strstr(row[3], "dump/reload table");
437           if (dump_txt && !repair_txt)
438           {
439             found_error= 0;
440             table_rebuild= 0;
441             prev_alter[0]= '\0';
442             failed_tables.push_back(row[0]);
443           }
444         }
445       }
446     }
447     else
448       printf("%-9s: %s", row[2], row[3]);
449 
450     my_stpcpy(prev, row[0]);
451     putchar('\n');
452   }
453   /* add the last table to be repaired to the list */
454   if (found_error && opt_auto_repair && what_to_do != DO_REPAIR)
455   {
456     if (table_rebuild)
457     {
458       if (prev_alter[0])
459         alter_table_cmds.push_back(prev_alter);
460       else
461         tables4rebuild.push_back(escape_db_table_name(prev, dot_pos));
462     }
463     else
464     {
465       tables4repair.push_back(escape_db_table_name(prev, dot_pos));
466     }
467   }
468   mysql_free_result(res);
469 }
470 
mysql_check(MYSQL * connection,int what_to_do,my_bool opt_alldbs,my_bool opt_check_only_changed,my_bool opt_extended,my_bool opt_databases,my_bool opt_fast,my_bool opt_medium_check,my_bool opt_quick,my_bool opt_all_in_1,my_bool opt_silent,my_bool opt_auto_repair,my_bool ignore_errors,my_bool opt_frm,my_bool opt_fix_table_names,my_bool opt_fix_db_names,my_bool opt_upgrade,my_bool opt_write_binlog,uint verbose,string opt_skip_database,vector<string> arguments,void (* dberror)(MYSQL * mysql,string when))471 void Mysql::Tools::Check::mysql_check(MYSQL* connection, int what_to_do,
472                 my_bool opt_alldbs,
473                 my_bool opt_check_only_changed, my_bool opt_extended,
474                 my_bool opt_databases, my_bool opt_fast,
475                 my_bool opt_medium_check, my_bool opt_quick,
476                 my_bool opt_all_in_1, my_bool opt_silent,
477                 my_bool opt_auto_repair, my_bool ignore_errors,
478                 my_bool opt_frm, my_bool opt_fix_table_names,
479                 my_bool opt_fix_db_names, my_bool opt_upgrade,
480                 my_bool opt_write_binlog, uint verbose,
481                 string opt_skip_database, vector<string> arguments,
482                 void (*dberror)(MYSQL *mysql, string when))
483 {
484   ::sock= connection;
485   ::what_to_do= what_to_do;
486   ::opt_alldbs= opt_alldbs;
487   ::opt_check_only_changed= opt_check_only_changed;
488   ::opt_extended= opt_extended;
489   ::opt_databases= opt_databases;
490   ::opt_fast= opt_fast;
491   ::opt_medium_check= opt_medium_check;
492   ::opt_quick= opt_quick;
493   ::opt_all_in_1= opt_all_in_1;
494   ::opt_silent= opt_silent;
495   ::opt_auto_repair= opt_auto_repair;
496   ::ignore_errors= ignore_errors;
497   ::opt_frm= opt_frm;
498   ::opt_fix_table_names= opt_fix_table_names;
499   ::opt_fix_db_names= opt_fix_db_names;
500   ::opt_upgrade= opt_upgrade;
501   ::opt_write_binlog= opt_write_binlog;
502   ::verbose= verbose;
503   ::opt_skip_database= opt_skip_database;
504   ::DBError= dberror;
505 
506   if (!::opt_write_binlog)
507   {
508     if (disable_binlog()) {
509       first_error= 1;
510       return;
511     }
512   }
513 
514   if (::opt_alldbs)
515     process_all_databases();
516   /* Only one database and selected table(s) */
517   else if (arguments.size() > 1 && !::opt_databases)
518   {
519     string db_name= arguments[0];
520     arguments.erase(arguments.begin());
521     process_selected_tables(db_name, arguments);
522   }
523   /* One or more databases, all tables */
524   else
525     process_databases(arguments);
526   if (::opt_auto_repair)
527   {
528     if (!::opt_silent)
529     {
530       if (!(tables4repair.empty() && tables4rebuild.empty()))
531         puts("\nRepairing tables");
532 
533       if (!(alter_table_cmds.empty()))
534         puts("\nUpgrading tables");
535     }
536 
537     ::what_to_do = DO_REPAIR;
538 
539     vector<string>::iterator it;
540     for (it = tables4repair.begin(); it != tables4repair.end() ; it++)
541     {
542       handle_request_for_tables(*it);
543     }
544     for (it = tables4rebuild.begin(); it != tables4rebuild.end() ; it++)
545     {
546       rebuild_table(*it);
547     }
548     for (it = alter_table_cmds.begin(); it != alter_table_cmds.end() ; it++)
549     {
550       if (0 == run_query(*it))
551         printf("Running  : %s\nstatus   : OK\n", (*it).c_str());
552       else
553       {
554         fprintf(stderr, "Failed to %s\n", (*it).c_str());
555         fprintf(stderr, "Error: %s\n", mysql_error(sock));
556       }
557     }
558     if (!failed_tables.empty())
559     {
560       fprintf(stderr,
561               "These tables cannot be automatically upgraded,"
562               " see the log above:\n");
563     }
564     for (it = failed_tables.begin(); it != failed_tables.end() ; it++)
565     {
566       fprintf(stderr, "%s\n", it->c_str());
567     }
568   }
569 }
570 
Program()571 Program::Program()
572   : m_what_to_do(0),
573   m_auto_repair(false),
574   m_upgrade(false),
575   m_verbose(false),
576   m_ignore_errors(false),
577   m_write_binlog(false),
578   m_process_all_dbs(false),
579   m_fix_table_names(false),
580   m_fix_db_names(false),
581   m_connection(NULL),
582   m_error_callback(NULL)
583 {
584 }
585 
check_databases(MYSQL * connection,vector<string> databases)586 int Program::check_databases(MYSQL* connection, vector<string> databases)
587 {
588   this->m_connection= connection;
589   this->m_process_all_dbs= false;
590   return this->set_what_to_do(DO_CHECK)
591     ->execute(databases);
592 }
593 
check_all_databases(MYSQL * connection)594 int Program::check_all_databases(MYSQL* connection)
595 {
596   this->m_connection= connection;
597   this->m_process_all_dbs= true;
598   return this->set_what_to_do(DO_CHECK)
599     ->execute(vector<string>());
600 }
601 
upgrade_databases(MYSQL * connection,vector<string> databases)602 int Program::upgrade_databases(MYSQL* connection, vector<string> databases)
603 {
604   this->m_connection= connection;
605   this->m_process_all_dbs= false;
606   return this->set_what_to_do(DO_UPGRADE)
607     ->execute(databases);
608 }
609 
upgrade_all_databases(MYSQL * connection)610 int Program::upgrade_all_databases(MYSQL* connection)
611 {
612   this->m_connection= connection;
613   this->m_process_all_dbs= true;
614   return this->set_what_to_do(DO_UPGRADE)
615     ->execute(vector<string>());
616 }
617 
enable_auto_repair(bool enable)618 Program* Program::enable_auto_repair(bool enable)
619 {
620   this->m_auto_repair= enable;
621   return this;
622 }
623 
enable_upgrade(bool enable)624 Program* Program::enable_upgrade(bool enable)
625 {
626   this->m_upgrade= enable;
627   return this;
628 }
629 
enable_verbosity(bool enable)630 Program* Program::enable_verbosity(bool enable)
631 {
632   this->m_verbose= enable;
633   return this;
634 }
635 
enable_writing_binlog(bool enable)636 Program* Program::enable_writing_binlog(bool enable)
637 {
638   this->m_write_binlog= enable;
639   return this;
640 }
641 
enable_fixing_table_names(bool enable)642 Program* Program::enable_fixing_table_names(bool enable)
643 {
644   this->m_fix_table_names= enable;
645   return this;
646 }
647 
enable_fixing_db_names(bool enable)648 Program* Program::enable_fixing_db_names(bool enable)
649 {
650   this->m_fix_db_names= enable;
651   return this;
652 }
653 
set_ignore_errors(bool ignore)654 Program* Program::set_ignore_errors(bool ignore)
655 {
656   this->m_ignore_errors= ignore;
657   return this;
658 }
659 
set_skip_database(string database)660 Program* Program::set_skip_database(string database)
661 {
662   this->m_database_to_skip= database;
663   return this;
664 }
665 
set_error_callback(void (* error_callback)(MYSQL * mysql,string when))666 Program* Program::set_error_callback(void (
667   *error_callback)(MYSQL *mysql, string when))
668 {
669   this->m_error_callback= error_callback;
670   return this;
671 }
672 
set_what_to_do(int functionality)673 Program* Program::set_what_to_do(int functionality)
674 {
675   this->m_what_to_do= functionality;
676   return this;
677 }
678 
execute(vector<string> positional_options)679 int Program::execute(vector<string> positional_options)
680 {
681   Mysql::Tools::Check::mysql_check(
682     this->m_connection, // connection
683     this->m_what_to_do, // what_to_do
684     this->m_process_all_dbs, // opt_alldbs
685     false, // opt_check_only_changed
686     false, // opt_extended
687     !this->m_process_all_dbs, // opt_databases
688     false, // opt_fast
689     false, // opt_medium_check
690     false, // opt_quick
691     false, // opt_all_in_1
692     false, // opt_silent
693     this->m_auto_repair, // opt_auto_repair
694     this->m_ignore_errors, // ignore_errors
695     false, // opt_frm
696     this->m_fix_table_names, // opt_fix_table_names
697     this->m_fix_db_names, // opt_fix_db_names
698     this->m_upgrade, // opt_upgrade
699     this->m_write_binlog, // opt_write_binlog
700     this->m_verbose, // verbose
701     this->m_database_to_skip,
702     positional_options,
703     this->m_error_callback);
704   return 0;
705 }
706 
707