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